Merge "Improve aarch64 MonitorEntry/Exit assembly code"
diff --git a/Android.mk b/Android.mk
index f3ab3c1..0e86188 100644
--- a/Android.mk
+++ b/Android.mk
@@ -42,7 +42,7 @@
 
 .PHONY: clean-oat-host
 clean-oat-host:
-	find $(OUT_DIR) -name "*.oat" -o -name "*.odex" -o -name "*.art" | xargs rm -f
+	find $(OUT_DIR) -name "*.oat" -o -name "*.odex" -o -name "*.art" -o -name '*.vdex' | xargs rm -f
 ifneq ($(TMPDIR),)
 	rm -rf $(TMPDIR)/$(USER)/test-*/dalvik-cache/*
 	rm -rf $(TMPDIR)/android-data/dalvik-cache/*
diff --git a/build/Android.bp b/build/Android.bp
index b1553c7..6c9f1d4 100644
--- a/build/Android.bp
+++ b/build/Android.bp
@@ -146,6 +146,7 @@
         "external/valgrind",
         "external/vixl/src",
         "external/zlib",
+        "libnativehelper/platform_include"
     ],
 
     tidy_checks: [
diff --git a/build/Android.common_test.mk b/build/Android.common_test.mk
index b7a2379..1591e34 100644
--- a/build/Android.common_test.mk
+++ b/build/Android.common_test.mk
@@ -66,9 +66,6 @@
 # Do you want to test the optimizing compiler with graph coloring register allocation?
 ART_TEST_OPTIMIZING_GRAPH_COLOR ?= $(ART_TEST_FULL)
 
-# Do we want to test a non-PIC-compiled core image?
-ART_TEST_NPIC_IMAGE ?= $(ART_TEST_FULL)
-
 # Do we want to test PIC-compiled tests ("apps")?
 ART_TEST_PIC_TEST ?= $(ART_TEST_FULL)
 
diff --git a/build/Android.gtest.mk b/build/Android.gtest.mk
index c319b1a..b661e00 100644
--- a/build/Android.gtest.mk
+++ b/build/Android.gtest.mk
@@ -123,16 +123,16 @@
 ART_GTEST_elf_writer_test_TARGET_DEPS := $(TARGET_CORE_IMAGE_DEFAULT_64) $(TARGET_CORE_IMAGE_DEFAULT_32)
 
 ART_GTEST_dex2oat_environment_tests_HOST_DEPS := \
-  $(HOST_CORE_IMAGE_optimizing_pic_64) \
-  $(HOST_CORE_IMAGE_optimizing_pic_32) \
-  $(HOST_CORE_IMAGE_interpreter_pic_64) \
-  $(HOST_CORE_IMAGE_interpreter_pic_32) \
+  $(HOST_CORE_IMAGE_optimizing_64) \
+  $(HOST_CORE_IMAGE_optimizing_32) \
+  $(HOST_CORE_IMAGE_interpreter_64) \
+  $(HOST_CORE_IMAGE_interpreter_32) \
   $(HOST_OUT_EXECUTABLES)/patchoatd
 ART_GTEST_dex2oat_environment_tests_TARGET_DEPS := \
-  $(TARGET_CORE_IMAGE_optimizing_pic_64) \
-  $(TARGET_CORE_IMAGE_optimizing_pic_32) \
-  $(TARGET_CORE_IMAGE_interpreter_pic_64) \
-  $(TARGET_CORE_IMAGE_interpreter_pic_32) \
+  $(TARGET_CORE_IMAGE_optimizing_64) \
+  $(TARGET_CORE_IMAGE_optimizing_32) \
+  $(TARGET_CORE_IMAGE_interpreter_64) \
+  $(TARGET_CORE_IMAGE_interpreter_32) \
   $(TARGET_OUT_EXECUTABLES)/patchoatd
 
 ART_GTEST_oat_file_assistant_test_HOST_DEPS := \
diff --git a/build/Android.oat.mk b/build/Android.oat.mk
index 0086660..f53740e 100644
--- a/build/Android.oat.mk
+++ b/build/Android.oat.mk
@@ -38,11 +38,10 @@
 
 # Use dex2oat debug version for better error reporting
 # $(1): compiler - optimizing, interpreter or interpreter-access-checks.
-# $(2): pic/no-pic
-# $(3): 2ND_ or undefined, 2ND_ for 32-bit host builds.
-# $(4): wrapper, e.g., valgrind.
-# $(5): dex2oat suffix, e.g, valgrind requires 32 right now.
-# $(6): multi-image.
+# $(2): 2ND_ or undefined, 2ND_ for 32-bit host builds.
+# $(3): wrapper, e.g., valgrind.
+# $(4): dex2oat suffix, e.g, valgrind requires 32 right now.
+# $(5): multi-image.
 # NB depending on HOST_CORE_DEX_LOCATIONS so we are sure to have the dex files in frameworks for
 # run-test --no-image
 define create-core-oat-host-rules
@@ -50,7 +49,6 @@
   core_image_name :=
   core_oat_name :=
   core_infix :=
-  core_pic_infix :=
   core_dex2oat_dependency := $(DEX2OAT_DEPENDENCY)
 
   ifeq ($(1),optimizing)
@@ -70,19 +68,8 @@
     $$(error found $(1) expected interpreter, interpreter-access-checks, or optimizing)
   endif
 
-  ifeq ($(2),pic)
-    core_compile_options += --compile-pic
-  endif
-  ifeq ($(2),no-pic)
-    core_pic_infix := -npic
-  endif
-  ifneq ($(filter-out pic no-pic,$(2)),)
-    # Technically this test is not precise, but hopefully good enough.
-    $$(error found $(2) expected pic or no-pic)
-  endif
-
-  # If $(6) is true, generate a multi-image.
-  ifeq ($(6),true)
+  # If $(5) is true, generate a multi-image.
+  ifeq ($(5),true)
     core_multi_infix := -multi
     core_multi_param := --multi-image --no-inline-from=core-oj-hostdex.jar
     core_multi_group := _multi
@@ -92,20 +79,20 @@
     core_multi_group :=
   endif
 
-  core_image_name := $($(3)HOST_CORE_IMG_OUT_BASE)$$(core_infix)$$(core_pic_infix)$$(core_multi_infix)$(4)$(CORE_IMG_SUFFIX)
-  core_oat_name := $($(3)HOST_CORE_OAT_OUT_BASE)$$(core_infix)$$(core_pic_infix)$$(core_multi_infix)$(4)$(CORE_OAT_SUFFIX)
+  core_image_name := $($(2)HOST_CORE_IMG_OUT_BASE)$$(core_infix)$$(core_multi_infix)$(3)$(CORE_IMG_SUFFIX)
+  core_oat_name := $($(2)HOST_CORE_OAT_OUT_BASE)$$(core_infix)$$(core_multi_infix)$(3)$(CORE_OAT_SUFFIX)
 
   # Using the bitness suffix makes it easier to add as a dependency for the run-test mk.
-  ifeq ($(3),)
-    $(4)HOST_CORE_IMAGE_$(1)_$(2)$$(core_multi_group)_64 := $$(core_image_name)
+  ifeq ($(2),)
+    $(3)HOST_CORE_IMAGE_$(1)$$(core_multi_group)_64 := $$(core_image_name)
   else
-    $(4)HOST_CORE_IMAGE_$(1)_$(2)$$(core_multi_group)_32 := $$(core_image_name)
+    $(3)HOST_CORE_IMAGE_$(1)$$(core_multi_group)_32 := $$(core_image_name)
   endif
-  $(4)HOST_CORE_IMG_OUTS += $$(core_image_name)
-  $(4)HOST_CORE_OAT_OUTS += $$(core_oat_name)
+  $(3)HOST_CORE_IMG_OUTS += $$(core_image_name)
+  $(3)HOST_CORE_OAT_OUTS += $$(core_oat_name)
 
   # If we have a wrapper, make the target phony.
-  ifneq ($(4),)
+  ifneq ($(3),)
 .PHONY: $$(core_image_name)
   endif
 $$(core_image_name): PRIVATE_CORE_COMPILE_OPTIONS := $$(core_compile_options)
@@ -115,15 +102,15 @@
 $$(core_image_name): $$(HOST_CORE_DEX_LOCATIONS) $$(core_dex2oat_dependency)
 	@echo "host dex2oat: $$@"
 	@mkdir -p $$(dir $$@)
-	$$(hide) $(4) $$(DEX2OAT)$(5) --runtime-arg -Xms$(DEX2OAT_IMAGE_XMS) \
+	$$(hide) $(3) $$(DEX2OAT)$(4) --runtime-arg -Xms$(DEX2OAT_IMAGE_XMS) \
 	  --runtime-arg -Xmx$(DEX2OAT_IMAGE_XMX) \
 	  --image-classes=$$(PRELOADED_CLASSES) $$(addprefix --dex-file=,$$(HOST_CORE_DEX_FILES)) \
 	  $$(addprefix --dex-location=,$$(HOST_CORE_DEX_LOCATIONS)) --oat-file=$$(PRIVATE_CORE_OAT_NAME) \
 	  --oat-location=$$(PRIVATE_CORE_OAT_NAME) --image=$$(PRIVATE_CORE_IMG_NAME) \
-	  --base=$$(LIBART_IMG_HOST_BASE_ADDRESS) --instruction-set=$$($(3)ART_HOST_ARCH) \
-	  $$(LOCAL_$(3)DEX2OAT_HOST_INSTRUCTION_SET_FEATURES_OPTION) \
+	  --base=$$(LIBART_IMG_HOST_BASE_ADDRESS) --instruction-set=$$($(2)ART_HOST_ARCH) \
+	  $$(LOCAL_$(2)DEX2OAT_HOST_INSTRUCTION_SET_FEATURES_OPTION) \
 	  --host --android-root=$$(HOST_OUT) --include-patch-information \
-	  --generate-debug-info --generate-build-id \
+	  --generate-debug-info --generate-build-id --compile-pic \
 	  $$(PRIVATE_CORE_MULTI_PARAM) $$(PRIVATE_CORE_COMPILE_OPTIONS)
 
 $$(core_oat_name): $$(core_image_name)
@@ -134,7 +121,6 @@
   core_image_name :=
   core_oat_name :=
   core_infix :=
-  core_pic_infix :=
 endef  # create-core-oat-host-rules
 
 # $(1): compiler - optimizing, interpreter or interpreter-access-checks.
@@ -142,12 +128,10 @@
 # $(3): dex2oat suffix.
 # $(4): multi-image.
 define create-core-oat-host-rule-combination
-  $(call create-core-oat-host-rules,$(1),no-pic,,$(2),$(3),$(4))
-  $(call create-core-oat-host-rules,$(1),pic,,$(2),$(3),$(4))
+  $(call create-core-oat-host-rules,$(1),,$(2),$(3),$(4))
 
   ifneq ($(HOST_PREFER_32_BIT),true)
-    $(call create-core-oat-host-rules,$(1),no-pic,2ND_,$(2),$(3),$(4))
-    $(call create-core-oat-host-rules,$(1),pic,2ND_,$(2),$(3),$(4))
+    $(call create-core-oat-host-rules,$(1),2ND_,$(2),$(3),$(4))
   endif
 endef
 
@@ -173,7 +157,6 @@
   core_image_name :=
   core_oat_name :=
   core_infix :=
-  core_pic_infix :=
   core_dex2oat_dependency := $(DEX2OAT_DEPENDENCY)
 
   ifeq ($(1),optimizing)
@@ -195,35 +178,24 @@
     $$(error found $(1) expected interpreter, interpreter-access-checks, or optimizing)
   endif
 
-  ifeq ($(2),pic)
-    core_compile_options += --compile-pic
-  endif
-  ifeq ($(2),no-pic)
-    core_pic_infix := -npic
-  endif
-  ifneq ($(filter-out pic no-pic,$(2)),)
-    #Technically this test is not precise, but hopefully good enough.
-    $$(error found $(2) expected pic or no-pic)
-  endif
-
-  core_image_name := $($(3)TARGET_CORE_IMG_OUT_BASE)$$(core_infix)$$(core_pic_infix)$(4)$(CORE_IMG_SUFFIX)
-  core_oat_name := $($(3)TARGET_CORE_OAT_OUT_BASE)$$(core_infix)$$(core_pic_infix)$(4)$(CORE_OAT_SUFFIX)
+  core_image_name := $($(2)TARGET_CORE_IMG_OUT_BASE)$$(core_infix)$(3)$(CORE_IMG_SUFFIX)
+  core_oat_name := $($(2)TARGET_CORE_OAT_OUT_BASE)$$(core_infix)$(3)$(CORE_OAT_SUFFIX)
 
   # Using the bitness suffix makes it easier to add as a dependency for the run-test mk.
-  ifeq ($(3),)
+  ifeq ($(2),)
     ifdef TARGET_2ND_ARCH
-      $(4)TARGET_CORE_IMAGE_$(1)_$(2)_64 := $$(core_image_name)
+      $(3)TARGET_CORE_IMAGE_$(1)_64 := $$(core_image_name)
     else
-      $(4)TARGET_CORE_IMAGE_$(1)_$(2)_32 := $$(core_image_name)
+      $(3)TARGET_CORE_IMAGE_$(1)_32 := $$(core_image_name)
     endif
   else
-    $(4)TARGET_CORE_IMAGE_$(1)_$(2)_32 := $$(core_image_name)
+    $(3)TARGET_CORE_IMAGE_$(1)_32 := $$(core_image_name)
   endif
-  $(4)TARGET_CORE_IMG_OUTS += $$(core_image_name)
-  $(4)TARGET_CORE_OAT_OUTS += $$(core_oat_name)
+  $(3)TARGET_CORE_IMG_OUTS += $$(core_image_name)
+  $(3)TARGET_CORE_OAT_OUTS += $$(core_oat_name)
 
   # If we have a wrapper, make the target phony.
-  ifneq ($(4),)
+  ifneq ($(3),)
 .PHONY: $$(core_image_name)
   endif
 $$(core_image_name): PRIVATE_CORE_COMPILE_OPTIONS := $$(core_compile_options)
@@ -237,11 +209,11 @@
 	  --image-classes=$$(PRELOADED_CLASSES) $$(addprefix --dex-file=,$$(TARGET_CORE_DEX_FILES)) \
 	  $$(addprefix --dex-location=,$$(TARGET_CORE_DEX_LOCATIONS)) --oat-file=$$(PRIVATE_CORE_OAT_NAME) \
 	  --oat-location=$$(PRIVATE_CORE_OAT_NAME) --image=$$(PRIVATE_CORE_IMG_NAME) \
-	  --base=$$(LIBART_IMG_TARGET_BASE_ADDRESS) --instruction-set=$$($(3)TARGET_ARCH) \
-	  --instruction-set-variant=$$($(3)DEX2OAT_TARGET_CPU_VARIANT) \
-	  --instruction-set-features=$$($(3)DEX2OAT_TARGET_INSTRUCTION_SET_FEATURES) \
+	  --base=$$(LIBART_IMG_TARGET_BASE_ADDRESS) --instruction-set=$$($(2)TARGET_ARCH) \
+	  --instruction-set-variant=$$($(2)DEX2OAT_TARGET_CPU_VARIANT) \
+	  --instruction-set-features=$$($(2)DEX2OAT_TARGET_INSTRUCTION_SET_FEATURES) \
 	  --android-root=$$(PRODUCT_OUT)/system --include-patch-information \
-	  --generate-debug-info --generate-build-id \
+	  --generate-debug-info --generate-build-id --compile-pic \
 	  $$(PRIVATE_CORE_COMPILE_OPTIONS) || (rm $$(PRIVATE_CORE_OAT_NAME); exit 1)
 
 $$(core_oat_name): $$(core_image_name)
@@ -252,19 +224,16 @@
   core_image_name :=
   core_oat_name :=
   core_infix :=
-  core_pic_infix :=
 endef  # create-core-oat-target-rules
 
 # $(1): compiler - optimizing, interpreter or interpreter-access-checks.
 # $(2): wrapper.
 # $(3): dex2oat suffix.
 define create-core-oat-target-rule-combination
-  $(call create-core-oat-target-rules,$(1),no-pic,,$(2),$(3))
-  $(call create-core-oat-target-rules,$(1),pic,,$(2),$(3))
+  $(call create-core-oat-target-rules,$(1),,$(2),$(3))
 
   ifdef TARGET_2ND_ARCH
-    $(call create-core-oat-target-rules,$(1),no-pic,2ND_,$(2),$(3))
-    $(call create-core-oat-target-rules,$(1),pic,2ND_,$(2),$(3))
+    $(call create-core-oat-target-rules,$(1),2ND_,$(2),$(3))
   endif
 endef
 
@@ -284,7 +253,7 @@
 
 # Define a default core image that can be used for things like gtests that
 # need some image to run, but don't otherwise care which image is used.
-HOST_CORE_IMAGE_DEFAULT_32 := $(HOST_CORE_IMAGE_optimizing_pic_32)
-HOST_CORE_IMAGE_DEFAULT_64 := $(HOST_CORE_IMAGE_optimizing_pic_64)
-TARGET_CORE_IMAGE_DEFAULT_32 := $(TARGET_CORE_IMAGE_optimizing_pic_32)
-TARGET_CORE_IMAGE_DEFAULT_64 := $(TARGET_CORE_IMAGE_optimizing_pic_64)
+HOST_CORE_IMAGE_DEFAULT_32 := $(HOST_CORE_IMAGE_optimizing_32)
+HOST_CORE_IMAGE_DEFAULT_64 := $(HOST_CORE_IMAGE_optimizing_64)
+TARGET_CORE_IMAGE_DEFAULT_32 := $(TARGET_CORE_IMAGE_optimizing_32)
+TARGET_CORE_IMAGE_DEFAULT_64 := $(TARGET_CORE_IMAGE_optimizing_64)
diff --git a/build/art.go b/build/art.go
index e7f7e21..053968d 100644
--- a/build/art.go
+++ b/build/art.go
@@ -70,10 +70,10 @@
 			"-DART_READ_BARRIER_TYPE_IS_"+barrierType+"=1")
 	}
 
-	if envTrue(ctx, "ART_USE_VIXL_ARM_BACKEND") {
-		// Used to enable the new VIXL-based ARM code generator.
-		cflags = append(cflags, "-DART_USE_VIXL_ARM_BACKEND=1")
-		asflags = append(asflags, "-DART_USE_VIXL_ARM_BACKEND=1")
+	if envTrue(ctx, "ART_USE_OLD_ARM_BACKEND") {
+		// Used to enable the old, pre-VIXL ARM code generator.
+		cflags = append(cflags, "-DART_USE_OLD_ARM_BACKEND=1")
+		asflags = append(asflags, "-DART_USE_OLD_ARM_BACKEND=1")
 	}
 
 	return cflags, asflags
diff --git a/compiler/Android.bp b/compiler/Android.bp
index f6a4db4..f5589cd 100644
--- a/compiler/Android.bp
+++ b/compiler/Android.bp
@@ -111,6 +111,7 @@
                 "optimizing/instruction_simplifier_shared.cc",
                 "optimizing/intrinsics_arm.cc",
                 "optimizing/intrinsics_arm_vixl.cc",
+                "optimizing/nodes_shared.cc",
                 "utils/arm/assembler_arm.cc",
                 "utils/arm/assembler_arm_vixl.cc",
                 "utils/arm/assembler_thumb2.cc",
@@ -127,7 +128,6 @@
                 "optimizing/scheduler_arm64.cc",
                 "optimizing/instruction_simplifier_arm64.cc",
                 "optimizing/intrinsics_arm64.cc",
-                "optimizing/nodes_arm64.cc",
                 "utils/arm64/assembler_arm64.cc",
                 "utils/arm64/jni_macro_assembler_arm64.cc",
                 "utils/arm64/managed_register_arm64.cc",
diff --git a/compiler/common_compiler_test.cc b/compiler/common_compiler_test.cc
index 2f9164c..d89cdba 100644
--- a/compiler/common_compiler_test.cc
+++ b/compiler/common_compiler_test.cc
@@ -175,6 +175,7 @@
                                               InstructionSet isa,
                                               size_t number_of_threads) {
   compiler_options_->boot_image_ = true;
+  compiler_options_->SetCompilerFilter(GetCompilerFilter());
   compiler_driver_.reset(new CompilerDriver(compiler_options_.get(),
                                             verification_results_.get(),
                                             kind,
diff --git a/compiler/common_compiler_test.h b/compiler/common_compiler_test.h
index 0d45a50..98dcf20 100644
--- a/compiler/common_compiler_test.h
+++ b/compiler/common_compiler_test.h
@@ -77,6 +77,10 @@
 
   virtual ProfileCompilationInfo* GetProfileCompilationInfo();
 
+  virtual CompilerFilter::Filter GetCompilerFilter() const {
+    return CompilerFilter::kDefaultCompilerFilter;
+  }
+
   virtual void TearDown();
 
   void CompileClass(mirror::ClassLoader* class_loader, const char* class_name)
diff --git a/compiler/compiler.h b/compiler/compiler.h
index 2ca0b77..908d366 100644
--- a/compiler/compiler.h
+++ b/compiler/compiler.h
@@ -27,6 +27,7 @@
   class JitCodeCache;
 }
 namespace mirror {
+  class ClassLoader;
   class DexCache;
 }
 
@@ -63,7 +64,7 @@
                                   InvokeType invoke_type,
                                   uint16_t class_def_idx,
                                   uint32_t method_idx,
-                                  jobject class_loader,
+                                  Handle<mirror::ClassLoader> class_loader,
                                   const DexFile& dex_file,
                                   Handle<mirror::DexCache> dex_cache) const = 0;
 
diff --git a/compiler/dex/dex_to_dex_compiler.cc b/compiler/dex/dex_to_dex_compiler.cc
index d4f6545..76aeaa5 100644
--- a/compiler/dex/dex_to_dex_compiler.cc
+++ b/compiler/dex/dex_to_dex_compiler.cc
@@ -284,16 +284,13 @@
   }
   uint32_t method_idx = is_range ? inst->VRegB_3rc() : inst->VRegB_35c();
   ScopedObjectAccess soa(Thread::Current());
-  StackHandleScope<1> hs(soa.Self());
-  Handle<mirror::ClassLoader> class_loader(hs.NewHandle(
-      soa.Decode<mirror::ClassLoader>(unit_.GetClassLoader())));
 
   ClassLinker* class_linker = unit_.GetClassLinker();
   ArtMethod* resolved_method = class_linker->ResolveMethod<ClassLinker::kForceICCECheck>(
       GetDexFile(),
       method_idx,
       unit_.GetDexCache(),
-      class_loader,
+      unit_.GetClassLoader(),
       /* referrer */ nullptr,
       kVirtual);
 
@@ -330,7 +327,7 @@
     InvokeType invoke_type ATTRIBUTE_UNUSED,
     uint16_t class_def_idx,
     uint32_t method_idx,
-    jobject class_loader,
+    Handle<mirror::ClassLoader> class_loader,
     const DexFile& dex_file,
     DexToDexCompilationLevel dex_to_dex_compilation_level) {
   DCHECK(driver != nullptr);
diff --git a/compiler/dex/dex_to_dex_compiler.h b/compiler/dex/dex_to_dex_compiler.h
index 0a00d45..00c596d 100644
--- a/compiler/dex/dex_to_dex_compiler.h
+++ b/compiler/dex/dex_to_dex_compiler.h
@@ -18,6 +18,7 @@
 #define ART_COMPILER_DEX_DEX_TO_DEX_COMPILER_H_
 
 #include "dex_file.h"
+#include "handle.h"
 #include "invoke_type.h"
 
 namespace art {
@@ -25,6 +26,10 @@
 class CompiledMethod;
 class CompilerDriver;
 
+namespace mirror {
+class ClassLoader;
+}  // namespace mirror
+
 namespace optimizer {
 
 enum class DexToDexCompilationLevel {
@@ -40,7 +45,7 @@
                               InvokeType invoke_type,
                               uint16_t class_def_idx,
                               uint32_t method_idx,
-                              jobject class_loader,
+                              Handle<mirror::ClassLoader> class_loader,
                               const DexFile& dex_file,
                               DexToDexCompilationLevel dex_to_dex_compilation_level);
 
diff --git a/compiler/driver/compiler_driver-inl.h b/compiler/driver/compiler_driver-inl.h
index f296851..5823306 100644
--- a/compiler/driver/compiler_driver-inl.h
+++ b/compiler/driver/compiler_driver-inl.h
@@ -31,17 +31,12 @@
 
 namespace art {
 
-inline mirror::ClassLoader* CompilerDriver::GetClassLoader(const ScopedObjectAccess& soa,
-                                                           const DexCompilationUnit* mUnit) {
-  return soa.Decode<mirror::ClassLoader>(mUnit->GetClassLoader()).Ptr();
-}
-
 inline mirror::Class* CompilerDriver::ResolveClass(
     const ScopedObjectAccess& soa, Handle<mirror::DexCache> dex_cache,
     Handle<mirror::ClassLoader> class_loader, dex::TypeIndex cls_index,
     const DexCompilationUnit* mUnit) {
   DCHECK_EQ(dex_cache->GetDexFile(), mUnit->GetDexFile());
-  DCHECK_EQ(class_loader.Get(), GetClassLoader(soa, mUnit));
+  DCHECK_EQ(class_loader.Get(), mUnit->GetClassLoader().Get());
   mirror::Class* cls = mUnit->GetClassLinker()->ResolveType(
       *mUnit->GetDexFile(), cls_index, dex_cache, class_loader);
   DCHECK_EQ(cls == nullptr, soa.Self()->IsExceptionPending());
@@ -56,7 +51,7 @@
     const ScopedObjectAccess& soa, Handle<mirror::DexCache> dex_cache,
     Handle<mirror::ClassLoader> class_loader, const DexCompilationUnit* mUnit) {
   DCHECK_EQ(dex_cache->GetDexFile(), mUnit->GetDexFile());
-  DCHECK_EQ(class_loader.Get(), GetClassLoader(soa, mUnit));
+  DCHECK_EQ(class_loader.Get(), mUnit->GetClassLoader().Get());
   const DexFile::MethodId& referrer_method_id =
       mUnit->GetDexFile()->GetMethodId(mUnit->GetDexMethodIndex());
   return ResolveClass(soa, dex_cache, class_loader, referrer_method_id.class_idx_, mUnit);
@@ -87,7 +82,7 @@
     const ScopedObjectAccess& soa, Handle<mirror::DexCache> dex_cache,
     Handle<mirror::ClassLoader> class_loader, const DexCompilationUnit* mUnit,
     uint32_t field_idx, bool is_static) {
-  DCHECK_EQ(class_loader.Get(), GetClassLoader(soa, mUnit));
+  DCHECK_EQ(class_loader.Get(), mUnit->GetClassLoader().Get());
   return ResolveFieldWithDexFile(soa, dex_cache, class_loader, mUnit->GetDexFile(), field_idx,
                                  is_static);
 }
@@ -139,7 +134,7 @@
     ScopedObjectAccess& soa, Handle<mirror::DexCache> dex_cache,
     Handle<mirror::ClassLoader> class_loader, const DexCompilationUnit* mUnit,
     uint32_t method_idx, InvokeType invoke_type, bool check_incompatible_class_change) {
-  DCHECK_EQ(class_loader.Get(), GetClassLoader(soa, mUnit));
+  DCHECK_EQ(class_loader.Get(), mUnit->GetClassLoader().Get());
   ArtMethod* resolved_method =
       check_incompatible_class_change
           ? mUnit->GetClassLinker()->ResolveMethod<ClassLinker::kForceICCECheck>(
diff --git a/compiler/driver/compiler_driver.cc b/compiler/driver/compiler_driver.cc
index 26c0818..7e91453 100644
--- a/compiler/driver/compiler_driver.cc
+++ b/compiler/driver/compiler_driver.cc
@@ -580,7 +580,7 @@
                           InvokeType invoke_type,
                           uint16_t class_def_idx,
                           uint32_t method_idx,
-                          jobject class_loader,
+                          Handle<mirror::ClassLoader> class_loader,
                           const DexFile& dex_file,
                           optimizer::DexToDexCompilationLevel dex_to_dex_compilation_level,
                           bool compilation_enabled,
@@ -621,9 +621,6 @@
       // Look-up the ArtMethod associated with this code_item (if any)
       // -- It is later used to lookup any [optimization] annotations for this method.
       ScopedObjectAccess soa(self);
-      StackHandleScope<1> hs(soa.Self());
-      Handle<mirror::ClassLoader> class_loader_handle(hs.NewHandle(
-          soa.Decode<mirror::ClassLoader>(class_loader)));
 
       // TODO: Lookup annotation from DexFile directly without resolving method.
       ArtMethod* method =
@@ -631,7 +628,7 @@
               dex_file,
               method_idx,
               dex_cache,
-              class_loader_handle,
+              class_loader,
               /* referrer */ nullptr,
               invoke_type);
 
@@ -678,9 +675,14 @@
 
     if (compile) {
       // NOTE: if compiler declines to compile this method, it will return null.
-      compiled_method = driver->GetCompiler()->Compile(code_item, access_flags, invoke_type,
-                                                       class_def_idx, method_idx, class_loader,
-                                                       dex_file, dex_cache);
+      compiled_method = driver->GetCompiler()->Compile(code_item,
+                                                       access_flags,
+                                                       invoke_type,
+                                                       class_def_idx,
+                                                       method_idx,
+                                                       class_loader,
+                                                       dex_file,
+                                                       dex_cache);
     }
     if (compiled_method == nullptr &&
         dex_to_dex_compilation_level != optimizer::DexToDexCompilationLevel::kDontDexToDexCompile) {
@@ -727,12 +729,14 @@
   uint32_t method_idx = method->GetDexMethodIndex();
   uint32_t access_flags = method->GetAccessFlags();
   InvokeType invoke_type = method->GetInvokeType();
-  StackHandleScope<1> hs(self);
+  StackHandleScope<2> hs(self);
   Handle<mirror::DexCache> dex_cache(hs.NewHandle(method->GetDexCache()));
+  Handle<mirror::ClassLoader> class_loader(
+      hs.NewHandle(method->GetDeclaringClass()->GetClassLoader()));
   {
     ScopedObjectAccessUnchecked soa(self);
     ScopedLocalRef<jobject> local_class_loader(
-        soa.Env(), soa.AddLocalReference<jobject>(method->GetDeclaringClass()->GetClassLoader()));
+        soa.Env(), soa.AddLocalReference<jobject>(class_loader.Get()));
     jclass_loader = soa.Env()->NewGlobalRef(local_class_loader.get());
     // Find the dex_file
     dex_file = method->GetDexFile();
@@ -766,7 +770,7 @@
                 invoke_type,
                 class_def_idx,
                 method_idx,
-                jclass_loader,
+                class_loader,
                 *dex_file,
                 dex_to_dex_compilation_level,
                 true,
@@ -792,7 +796,7 @@
                   invoke_type,
                   class_def_idx,
                   method_idx,
-                  jclass_loader,
+                  class_loader,
                   *dex_file,
                   dex_to_dex_compilation_level,
                   true,
@@ -1050,11 +1054,16 @@
 }
 
 bool CompilerDriver::ShouldCompileBasedOnProfile(const MethodReference& method_ref) const {
-  if (profile_compilation_info_ == nullptr) {
-    // If we miss profile information it means that we don't do a profile guided compilation.
-    // Return true, and let the other filters decide if the method should be compiled.
+  // Profile compilation info may be null if no profile is passed.
+  if (!CompilerFilter::DependsOnProfile(compiler_options_->GetCompilerFilter())) {
+    // Use the compiler filter instead of the presence of profile_compilation_info_ since
+    // we may want to have full speed compilation along with profile based layout optimizations.
     return true;
   }
+  // If we are using a profile filter but do not have a profile compilation info, compile nothing.
+  if (profile_compilation_info_ == nullptr) {
+    return false;
+  }
   bool result = profile_compilation_info_->ContainsMethod(method_ref);
 
   if (kDebugProfileGuidedCompilation) {
@@ -1067,22 +1076,30 @@
 
 class ResolveCatchBlockExceptionsClassVisitor : public ClassVisitor {
  public:
-  explicit ResolveCatchBlockExceptionsClassVisitor(
-      std::set<std::pair<dex::TypeIndex, const DexFile*>>& exceptions_to_resolve)
-     : exceptions_to_resolve_(exceptions_to_resolve) {}
+  ResolveCatchBlockExceptionsClassVisitor() : classes_() {}
 
   virtual bool operator()(ObjPtr<mirror::Class> c) OVERRIDE REQUIRES_SHARED(Locks::mutator_lock_) {
-    const auto pointer_size = Runtime::Current()->GetClassLinker()->GetImagePointerSize();
-    for (auto& m : c->GetMethods(pointer_size)) {
-      ResolveExceptionsForMethod(&m);
-    }
+    classes_.push_back(c);
     return true;
   }
 
- private:
-  void ResolveExceptionsForMethod(ArtMethod* method_handle)
+  void FindExceptionTypesToResolve(
+      std::set<std::pair<dex::TypeIndex, const DexFile*>>* exceptions_to_resolve)
       REQUIRES_SHARED(Locks::mutator_lock_) {
-    const DexFile::CodeItem* code_item = method_handle->GetCodeItem();
+    const auto pointer_size = Runtime::Current()->GetClassLinker()->GetImagePointerSize();
+    for (ObjPtr<mirror::Class> klass : classes_) {
+      for (ArtMethod& method : klass->GetMethods(pointer_size)) {
+        FindExceptionTypesToResolveForMethod(&method, exceptions_to_resolve);
+      }
+    }
+  }
+
+ private:
+  void FindExceptionTypesToResolveForMethod(
+      ArtMethod* method,
+      std::set<std::pair<dex::TypeIndex, const DexFile*>>* exceptions_to_resolve)
+      REQUIRES_SHARED(Locks::mutator_lock_) {
+    const DexFile::CodeItem* code_item = method->GetCodeItem();
     if (code_item == nullptr) {
       return;  // native or abstract method
     }
@@ -1102,9 +1119,9 @@
         dex::TypeIndex encoded_catch_handler_handlers_type_idx =
             dex::TypeIndex(DecodeUnsignedLeb128(&encoded_catch_handler_list));
         // Add to set of types to resolve if not already in the dex cache resolved types
-        if (!method_handle->IsResolvedTypeIdx(encoded_catch_handler_handlers_type_idx)) {
-          exceptions_to_resolve_.emplace(encoded_catch_handler_handlers_type_idx,
-                                         method_handle->GetDexFile());
+        if (!method->IsResolvedTypeIdx(encoded_catch_handler_handlers_type_idx)) {
+          exceptions_to_resolve->emplace(encoded_catch_handler_handlers_type_idx,
+                                         method->GetDexFile());
         }
         // ignore address associated with catch handler
         DecodeUnsignedLeb128(&encoded_catch_handler_list);
@@ -1116,7 +1133,7 @@
     }
   }
 
-  std::set<std::pair<dex::TypeIndex, const DexFile*>>& exceptions_to_resolve_;
+  std::vector<ObjPtr<mirror::Class>> classes_;
 };
 
 class RecordImageClassesVisitor : public ClassVisitor {
@@ -1170,8 +1187,14 @@
       hs.NewHandle(class_linker->FindSystemClass(self, "Ljava/lang/Throwable;")));
   do {
     unresolved_exception_types.clear();
-    ResolveCatchBlockExceptionsClassVisitor visitor(unresolved_exception_types);
-    class_linker->VisitClasses(&visitor);
+    {
+      // Thread suspension is not allowed while ResolveCatchBlockExceptionsClassVisitor
+      // is using a std::vector<ObjPtr<mirror::Class>>.
+      ScopedAssertNoThreadSuspension ants(__FUNCTION__);
+      ResolveCatchBlockExceptionsClassVisitor visitor;
+      class_linker->VisitClasses(&visitor);
+      visitor.FindExceptionTypesToResolve(&unresolved_exception_types);
+    }
     for (const auto& exception_type : unresolved_exception_types) {
       dex::TypeIndex exception_type_idx = exception_type.first;
       const DexFile* dex_file = exception_type.second;
@@ -1422,19 +1445,14 @@
   dex_to_dex_references_.back().GetMethodIndexes().SetBit(method_ref.dex_method_index);
 }
 
-bool CompilerDriver::CanAccessTypeWithoutChecks(uint32_t referrer_idx,
-                                                Handle<mirror::DexCache> dex_cache,
-                                                dex::TypeIndex type_idx) {
-  // Get type from dex cache assuming it was populated by the verifier
-  mirror::Class* resolved_class = dex_cache->GetResolvedType(type_idx);
+bool CompilerDriver::CanAccessTypeWithoutChecks(ObjPtr<mirror::Class> referrer_class,
+                                                ObjPtr<mirror::Class> resolved_class) {
   if (resolved_class == nullptr) {
     stats_->TypeNeedsAccessCheck();
     return false;  // Unknown class needs access checks.
   }
-  const DexFile::MethodId& method_id = dex_cache->GetDexFile()->GetMethodId(referrer_idx);
   bool is_accessible = resolved_class->IsPublic();  // Public classes are always accessible.
   if (!is_accessible) {
-    mirror::Class* referrer_class = dex_cache->GetResolvedType(method_id.class_idx_);
     if (referrer_class == nullptr) {
       stats_->TypeNeedsAccessCheck();
       return false;  // Incomplete referrer knowledge needs access check.
@@ -1451,12 +1469,9 @@
   return is_accessible;
 }
 
-bool CompilerDriver::CanAccessInstantiableTypeWithoutChecks(uint32_t referrer_idx,
-                                                            Handle<mirror::DexCache> dex_cache,
-                                                            dex::TypeIndex type_idx,
+bool CompilerDriver::CanAccessInstantiableTypeWithoutChecks(ObjPtr<mirror::Class> referrer_class,
+                                                            ObjPtr<mirror::Class> resolved_class,
                                                             bool* finalizable) {
-  // Get type from dex cache assuming it was populated by the verifier.
-  mirror::Class* resolved_class = dex_cache->GetResolvedType(type_idx);
   if (resolved_class == nullptr) {
     stats_->TypeNeedsAccessCheck();
     // Be conservative.
@@ -1464,10 +1479,8 @@
     return false;  // Unknown class needs access checks.
   }
   *finalizable = resolved_class->IsFinalizable();
-  const DexFile::MethodId& method_id = dex_cache->GetDexFile()->GetMethodId(referrer_idx);
   bool is_accessible = resolved_class->IsPublic();  // Public classes are always accessible.
   if (!is_accessible) {
-    mirror::Class* referrer_class = dex_cache->GetResolvedType(method_id.class_idx_);
     if (referrer_class == nullptr) {
       stats_->TypeNeedsAccessCheck();
       return false;  // Incomplete referrer knowledge needs access check.
@@ -1511,9 +1524,7 @@
   mirror::Class* referrer_class;
   Handle<mirror::DexCache> dex_cache(mUnit->GetDexCache());
   {
-    StackHandleScope<1> hs(soa.Self());
-    Handle<mirror::ClassLoader> class_loader_handle(
-        hs.NewHandle(soa.Decode<mirror::ClassLoader>(mUnit->GetClassLoader())));
+    Handle<mirror::ClassLoader> class_loader_handle = mUnit->GetClassLoader();
     resolved_field = ResolveField(soa, dex_cache, class_loader_handle, mUnit, field_idx, false);
     referrer_class = resolved_field != nullptr
         ? ResolveCompilingMethodsClass(soa, dex_cache, class_loader_handle, mUnit) : nullptr;
@@ -2585,10 +2596,18 @@
         continue;
       }
       previous_direct_method_idx = method_idx;
-      CompileMethod(soa.Self(), driver, it.GetMethodCodeItem(), it.GetMethodAccessFlags(),
-                    it.GetMethodInvokeType(class_def), class_def_index,
-                    method_idx, jclass_loader, dex_file, dex_to_dex_compilation_level,
-                    compilation_enabled, dex_cache);
+      CompileMethod(soa.Self(),
+                    driver,
+                    it.GetMethodCodeItem(),
+                    it.GetMethodAccessFlags(),
+                    it.GetMethodInvokeType(class_def),
+                    class_def_index,
+                    method_idx,
+                    class_loader,
+                    dex_file,
+                    dex_to_dex_compilation_level,
+                    compilation_enabled,
+                    dex_cache);
       it.Next();
     }
     // Compile virtual methods
@@ -2602,10 +2621,17 @@
         continue;
       }
       previous_virtual_method_idx = method_idx;
-      CompileMethod(soa.Self(), driver, it.GetMethodCodeItem(), it.GetMethodAccessFlags(),
-                    it.GetMethodInvokeType(class_def), class_def_index,
-                    method_idx, jclass_loader, dex_file, dex_to_dex_compilation_level,
-                    compilation_enabled, dex_cache);
+      CompileMethod(soa.Self(),
+                    driver, it.GetMethodCodeItem(),
+                    it.GetMethodAccessFlags(),
+                    it.GetMethodInvokeType(class_def),
+                    class_def_index,
+                    method_idx,
+                    class_loader,
+                    dex_file,
+                    dex_to_dex_compilation_level,
+                    compilation_enabled,
+                    dex_cache);
       it.Next();
     }
     DCHECK(!it.HasNext());
diff --git a/compiler/driver/compiler_driver.h b/compiler/driver/compiler_driver.h
index 5b4c751..1e5c43d 100644
--- a/compiler/driver/compiler_driver.h
+++ b/compiler/driver/compiler_driver.h
@@ -187,16 +187,14 @@
       REQUIRES(!requires_constructor_barrier_lock_);
 
   // Are runtime access checks necessary in the compiled code?
-  bool CanAccessTypeWithoutChecks(uint32_t referrer_idx,
-                                  Handle<mirror::DexCache> dex_cache,
-                                  dex::TypeIndex type_idx)
+  bool CanAccessTypeWithoutChecks(ObjPtr<mirror::Class> referrer_class,
+                                  ObjPtr<mirror::Class> resolved_class)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
   // Are runtime access and instantiable checks necessary in the code?
   // out_is_finalizable is set to whether the type is finalizable.
-  bool CanAccessInstantiableTypeWithoutChecks(uint32_t referrer_idx,
-                                              Handle<mirror::DexCache> dex_cache,
-                                              dex::TypeIndex type_idx,
+  bool CanAccessInstantiableTypeWithoutChecks(ObjPtr<mirror::Class> referrer_class,
+                                              ObjPtr<mirror::Class> resolved_class,
                                               bool* out_is_finalizable)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
@@ -370,10 +368,6 @@
                                       uint32_t field_idx)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
-  mirror::ClassLoader* GetClassLoader(const ScopedObjectAccess& soa,
-                                      const DexCompilationUnit* mUnit)
-    REQUIRES_SHARED(Locks::mutator_lock_);
-
  private:
   void PreCompile(jobject class_loader,
                   const std::vector<const DexFile*>& dex_files,
diff --git a/compiler/driver/compiler_driver_test.cc b/compiler/driver/compiler_driver_test.cc
index 1e4ca16..97954f3 100644
--- a/compiler/driver/compiler_driver_test.cc
+++ b/compiler/driver/compiler_driver_test.cc
@@ -101,6 +101,7 @@
 };
 
 // Disabled due to 10 second runtime on host
+// TODO: Update the test for hash-based dex cache arrays. Bug: 30627598
 TEST_F(CompilerDriverTest, DISABLED_LARGE_CompileDexLibCore) {
   CompileAll(nullptr);
 
@@ -246,6 +247,11 @@
     return &profile_info_;
   }
 
+  CompilerFilter::Filter GetCompilerFilter() const OVERRIDE {
+    // Use a profile based filter.
+    return CompilerFilter::kSpeedProfile;
+  }
+
   std::unordered_set<std::string> GetExpectedMethodsForClass(const std::string& clazz) {
     if (clazz == "Main") {
       return std::unordered_set<std::string>({
@@ -304,7 +310,6 @@
 
   // Need to enable dex-file writability. Methods rejected to be compiled will run through the
   // dex-to-dex compiler.
-  ProfileCompilationInfo info;
   for (const DexFile* dex_file : GetDexFiles(class_loader)) {
     ASSERT_TRUE(dex_file->EnableWrite());
   }
diff --git a/compiler/driver/dex_compilation_unit.cc b/compiler/driver/dex_compilation_unit.cc
index 47b1929..7e8e812 100644
--- a/compiler/driver/dex_compilation_unit.cc
+++ b/compiler/driver/dex_compilation_unit.cc
@@ -21,7 +21,7 @@
 
 namespace art {
 
-DexCompilationUnit::DexCompilationUnit(jobject class_loader,
+DexCompilationUnit::DexCompilationUnit(Handle<mirror::ClassLoader> class_loader,
                                        ClassLinker* class_linker,
                                        const DexFile& dex_file,
                                        const DexFile::CodeItem* code_item,
diff --git a/compiler/driver/dex_compilation_unit.h b/compiler/driver/dex_compilation_unit.h
index 854927d..24a9a5b 100644
--- a/compiler/driver/dex_compilation_unit.h
+++ b/compiler/driver/dex_compilation_unit.h
@@ -34,7 +34,7 @@
 
 class DexCompilationUnit : public DeletableArenaObject<kArenaAllocMisc> {
  public:
-  DexCompilationUnit(jobject class_loader,
+  DexCompilationUnit(Handle<mirror::ClassLoader> class_loader,
                      ClassLinker* class_linker,
                      const DexFile& dex_file,
                      const DexFile::CodeItem* code_item,
@@ -44,7 +44,7 @@
                      const VerifiedMethod* verified_method,
                      Handle<mirror::DexCache> dex_cache);
 
-  jobject GetClassLoader() const {
+  Handle<mirror::ClassLoader> GetClassLoader() const {
     return class_loader_;
   }
 
@@ -113,7 +113,7 @@
   }
 
  private:
-  const jobject class_loader_;
+  const Handle<mirror::ClassLoader> class_loader_;
 
   ClassLinker* const class_linker_;
 
@@ -125,7 +125,7 @@
   const uint32_t access_flags_;
   const VerifiedMethod* verified_method_;
 
-  Handle<mirror::DexCache> dex_cache_;
+  const Handle<mirror::DexCache> dex_cache_;
 
   std::string symbol_;
 };
diff --git a/compiler/image_writer.cc b/compiler/image_writer.cc
index d2dd30d..117d113 100644
--- a/compiler/image_writer.cc
+++ b/compiler/image_writer.cc
@@ -940,9 +940,11 @@
     }
     ObjPtr<mirror::DexCache> dex_cache = self->DecodeJObject(data.weak_root)->AsDexCache();
     for (size_t i = 0; i < dex_cache->NumResolvedTypes(); i++) {
-      Class* klass = dex_cache->GetResolvedType(dex::TypeIndex(i));
+      mirror::TypeDexCachePair pair =
+          dex_cache->GetResolvedTypes()[i].load(std::memory_order_relaxed);
+      mirror::Class* klass = pair.object.Read();
       if (klass != nullptr && !KeepClass(klass)) {
-        dex_cache->SetResolvedType(dex::TypeIndex(i), nullptr);
+        dex_cache->ClearResolvedType(dex::TypeIndex(pair.index));
       }
     }
     ArtMethod** resolved_methods = dex_cache->GetResolvedMethods();
@@ -1922,8 +1924,7 @@
     // above comment for intern tables.
     ClassTable temp_class_table;
     temp_class_table.ReadFromMemory(class_table_memory_ptr);
-    CHECK_EQ(class_loaders_.size(), compile_app_image_ ? 1u : 0u);
-    mirror::ClassLoader* class_loader = compile_app_image_ ? *class_loaders_.begin() : nullptr;
+    ObjPtr<mirror::ClassLoader> class_loader = GetClassLoader();
     CHECK_EQ(temp_class_table.NumZygoteClasses(class_loader),
              table->NumNonZygoteClasses(class_loader) + table->NumZygoteClasses(class_loader));
     UnbufferedRootVisitor visitor(&root_visitor, RootInfo(kRootUnknown));
@@ -2213,7 +2214,7 @@
     orig_dex_cache->FixupStrings(NativeCopyLocation(orig_strings, orig_dex_cache),
                                  ImageAddressVisitor(this));
   }
-  GcRoot<mirror::Class>* orig_types = orig_dex_cache->GetResolvedTypes();
+  mirror::TypeDexCacheType* orig_types = orig_dex_cache->GetResolvedTypes();
   if (orig_types != nullptr) {
     copy_dex_cache->SetFieldPtrWithSize<false>(mirror::DexCache::ResolvedTypesOffset(),
                                                NativeLocationInImage(orig_types),
diff --git a/compiler/image_writer.h b/compiler/image_writer.h
index cc7df1c..bdc7146 100644
--- a/compiler/image_writer.h
+++ b/compiler/image_writer.h
@@ -51,8 +51,13 @@
 }  // namespace space
 }  // namespace gc
 
+namespace mirror {
+class ClassLoader;
+}  // namespace mirror
+
 class ClassLoaderVisitor;
 class ClassTable;
+class ImtConflictTable;
 
 static constexpr int kInvalidFd = -1;
 
@@ -79,6 +84,11 @@
     return true;
   }
 
+  ObjPtr<mirror::ClassLoader> GetClassLoader() {
+    CHECK_EQ(class_loaders_.size(), compile_app_image_ ? 1u : 0u);
+    return compile_app_image_ ? *class_loaders_.begin() : nullptr;
+  }
+
   template <typename T>
   T* GetImageAddress(T* object) const REQUIRES_SHARED(Locks::mutator_lock_) {
     if (object == nullptr || IsInBootImage(object)) {
diff --git a/compiler/oat_writer.cc b/compiler/oat_writer.cc
index 7c0cdbf..0ea1125 100644
--- a/compiler/oat_writer.cc
+++ b/compiler/oat_writer.cc
@@ -1060,6 +1060,7 @@
   WriteCodeMethodVisitor(OatWriter* writer, OutputStream* out, const size_t file_offset,
                          size_t relative_offset) SHARED_LOCK_FUNCTION(Locks::mutator_lock_)
     : OatDexMethodVisitor(writer, relative_offset),
+      class_loader_(writer->HasImage() ? writer->image_writer_->GetClassLoader() : nullptr),
       out_(out),
       file_offset_(file_offset),
       soa_(Thread::Current()),
@@ -1245,6 +1246,7 @@
   }
 
  private:
+  ObjPtr<mirror::ClassLoader> class_loader_;
   OutputStream* const out_;
   const size_t file_offset_;
   const ScopedObjectAccess soa_;
@@ -1303,10 +1305,12 @@
   }
 
   mirror::Class* GetTargetType(const LinkerPatch& patch) REQUIRES_SHARED(Locks::mutator_lock_) {
+    DCHECK(writer_->HasImage());
     ObjPtr<mirror::DexCache> dex_cache = GetDexCache(patch.TargetTypeDexFile());
-    mirror::Class* type = dex_cache->GetResolvedType(patch.TargetTypeIndex());
+    ObjPtr<mirror::Class> type =
+        ClassLinker::LookupResolvedType(patch.TargetTypeIndex(), dex_cache, class_loader_);
     CHECK(type != nullptr);
-    return type;
+    return type.Ptr();
   }
 
   mirror::String* GetTargetString(const LinkerPatch& patch) REQUIRES_SHARED(Locks::mutator_lock_) {
diff --git a/compiler/optimizing/builder.h b/compiler/optimizing/builder.h
index e4ad422..3a4c9db 100644
--- a/compiler/optimizing/builder.h
+++ b/compiler/optimizing/builder.h
@@ -54,7 +54,10 @@
         compiler_driver_(driver),
         compilation_stats_(compiler_stats),
         block_builder_(graph, dex_file, code_item),
-        ssa_builder_(graph, dex_compilation_unit->GetDexCache(), handles),
+        ssa_builder_(graph,
+                     dex_compilation_unit->GetClassLoader(),
+                     dex_compilation_unit->GetDexCache(),
+                     handles),
         instruction_builder_(graph,
                              &block_builder_,
                              &ssa_builder_,
@@ -80,10 +83,12 @@
         code_item_(code_item),
         dex_compilation_unit_(nullptr),
         compiler_driver_(nullptr),
-        null_dex_cache_(),
         compilation_stats_(nullptr),
         block_builder_(graph, nullptr, code_item),
-        ssa_builder_(graph, null_dex_cache_, handles),
+        ssa_builder_(graph,
+                     handles->NewHandle<mirror::ClassLoader>(nullptr),
+                     handles->NewHandle<mirror::DexCache>(nullptr),
+                     handles),
         instruction_builder_(graph,
                              &block_builder_,
                              &ssa_builder_,
@@ -96,7 +101,7 @@
                              /* code_generator */ nullptr,
                              /* interpreter_metadata */ nullptr,
                              /* compiler_stats */ nullptr,
-                             null_dex_cache_,
+                             handles->NewHandle<mirror::DexCache>(nullptr),
                              handles) {}
 
   GraphAnalysisResult BuildGraph();
@@ -117,8 +122,6 @@
 
   CompilerDriver* const compiler_driver_;
 
-  ScopedNullHandle<mirror::DexCache> null_dex_cache_;
-
   OptimizingCompilerStats* compilation_stats_;
 
   HBasicBlockBuilder block_builder_;
diff --git a/compiler/optimizing/code_generator_arm.cc b/compiler/optimizing/code_generator_arm.cc
index 759a951..7b84ef8 100644
--- a/compiler/optimizing/code_generator_arm.cc
+++ b/compiler/optimizing/code_generator_arm.cc
@@ -19,6 +19,7 @@
 #include "arch/arm/instruction_set_features_arm.h"
 #include "art_method.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"
@@ -1132,10 +1133,6 @@
   DISALLOW_COPY_AND_ASSIGN(ReadBarrierForRootSlowPathARM);
 };
 
-#undef __
-// NOLINT on __ macro to suppress wrong warning/fix (misc-macro-parentheses) from clang-tidy.
-#define __ down_cast<ArmAssembler*>(GetAssembler())->  // NOLINT
-
 inline Condition ARMCondition(IfCondition cond) {
   switch (cond) {
     case kCondEQ: return EQ;
@@ -1191,6 +1188,197 @@
   }
 }
 
+inline Shift ShiftFromOpKind(HDataProcWithShifterOp::OpKind op_kind) {
+  switch (op_kind) {
+    case HDataProcWithShifterOp::kASR: return ASR;
+    case HDataProcWithShifterOp::kLSL: return LSL;
+    case HDataProcWithShifterOp::kLSR: return LSR;
+    default:
+      LOG(FATAL) << "Unexpected op kind " << op_kind;
+      UNREACHABLE();
+  }
+}
+
+static void GenerateDataProcInstruction(HInstruction::InstructionKind kind,
+                                        Register out,
+                                        Register first,
+                                        const ShifterOperand& second,
+                                        CodeGeneratorARM* codegen) {
+  if (second.IsImmediate() && second.GetImmediate() == 0) {
+    const ShifterOperand in = kind == HInstruction::kAnd
+        ? ShifterOperand(0)
+        : ShifterOperand(first);
+
+    __ mov(out, in);
+  } else {
+    switch (kind) {
+      case HInstruction::kAdd:
+        __ add(out, first, second);
+        break;
+      case HInstruction::kAnd:
+        __ and_(out, first, second);
+        break;
+      case HInstruction::kOr:
+        __ orr(out, first, second);
+        break;
+      case HInstruction::kSub:
+        __ sub(out, first, second);
+        break;
+      case HInstruction::kXor:
+        __ eor(out, first, second);
+        break;
+      default:
+        LOG(FATAL) << "Unexpected instruction kind: " << kind;
+        UNREACHABLE();
+    }
+  }
+}
+
+static void GenerateDataProc(HInstruction::InstructionKind kind,
+                             const Location& out,
+                             const Location& first,
+                             const ShifterOperand& second_lo,
+                             const ShifterOperand& second_hi,
+                             CodeGeneratorARM* codegen) {
+  const Register first_hi = first.AsRegisterPairHigh<Register>();
+  const Register first_lo = first.AsRegisterPairLow<Register>();
+  const Register out_hi = out.AsRegisterPairHigh<Register>();
+  const Register out_lo = out.AsRegisterPairLow<Register>();
+
+  if (kind == HInstruction::kAdd) {
+    __ adds(out_lo, first_lo, second_lo);
+    __ adc(out_hi, first_hi, second_hi);
+  } else if (kind == HInstruction::kSub) {
+    __ subs(out_lo, first_lo, second_lo);
+    __ sbc(out_hi, first_hi, second_hi);
+  } else {
+    GenerateDataProcInstruction(kind, out_lo, first_lo, second_lo, codegen);
+    GenerateDataProcInstruction(kind, out_hi, first_hi, second_hi, codegen);
+  }
+}
+
+static ShifterOperand GetShifterOperand(Register rm, Shift shift, uint32_t shift_imm) {
+  return shift_imm == 0 ? ShifterOperand(rm) : ShifterOperand(rm, shift, shift_imm);
+}
+
+static void GenerateLongDataProc(HDataProcWithShifterOp* instruction, CodeGeneratorARM* codegen) {
+  DCHECK_EQ(instruction->GetType(), Primitive::kPrimLong);
+  DCHECK(HDataProcWithShifterOp::IsShiftOp(instruction->GetOpKind()));
+
+  const LocationSummary* const locations = instruction->GetLocations();
+  const uint32_t shift_value = instruction->GetShiftAmount();
+  const HInstruction::InstructionKind kind = instruction->GetInstrKind();
+  const Location first = locations->InAt(0);
+  const Location second = locations->InAt(1);
+  const Location out = locations->Out();
+  const Register first_hi = first.AsRegisterPairHigh<Register>();
+  const Register first_lo = first.AsRegisterPairLow<Register>();
+  const Register out_hi = out.AsRegisterPairHigh<Register>();
+  const Register out_lo = out.AsRegisterPairLow<Register>();
+  const Register second_hi = second.AsRegisterPairHigh<Register>();
+  const Register second_lo = second.AsRegisterPairLow<Register>();
+  const Shift shift = ShiftFromOpKind(instruction->GetOpKind());
+
+  if (shift_value >= 32) {
+    if (shift == LSL) {
+      GenerateDataProcInstruction(kind,
+                                  out_hi,
+                                  first_hi,
+                                  ShifterOperand(second_lo, LSL, shift_value - 32),
+                                  codegen);
+      GenerateDataProcInstruction(kind,
+                                  out_lo,
+                                  first_lo,
+                                  ShifterOperand(0),
+                                  codegen);
+    } else if (shift == ASR) {
+      GenerateDataProc(kind,
+                       out,
+                       first,
+                       GetShifterOperand(second_hi, ASR, shift_value - 32),
+                       ShifterOperand(second_hi, ASR, 31),
+                       codegen);
+    } else {
+      DCHECK_EQ(shift, LSR);
+      GenerateDataProc(kind,
+                       out,
+                       first,
+                       GetShifterOperand(second_hi, LSR, shift_value - 32),
+                       ShifterOperand(0),
+                       codegen);
+    }
+  } else {
+    DCHECK_GT(shift_value, 1U);
+    DCHECK_LT(shift_value, 32U);
+
+    if (shift == LSL) {
+      // We are not doing this for HInstruction::kAdd because the output will require
+      // Location::kOutputOverlap; not applicable to other cases.
+      if (kind == HInstruction::kOr || kind == HInstruction::kXor) {
+        GenerateDataProcInstruction(kind,
+                                    out_hi,
+                                    first_hi,
+                                    ShifterOperand(second_hi, LSL, shift_value),
+                                    codegen);
+        GenerateDataProcInstruction(kind,
+                                    out_hi,
+                                    out_hi,
+                                    ShifterOperand(second_lo, LSR, 32 - shift_value),
+                                    codegen);
+        GenerateDataProcInstruction(kind,
+                                    out_lo,
+                                    first_lo,
+                                    ShifterOperand(second_lo, LSL, shift_value),
+                                    codegen);
+      } else {
+        __ Lsl(IP, second_hi, shift_value);
+        __ orr(IP, IP, ShifterOperand(second_lo, LSR, 32 - shift_value));
+        GenerateDataProc(kind,
+                         out,
+                         first,
+                         ShifterOperand(second_lo, LSL, shift_value),
+                         ShifterOperand(IP),
+                         codegen);
+      }
+    } else {
+      DCHECK(shift == ASR || shift == LSR);
+
+      // We are not doing this for HInstruction::kAdd because the output will require
+      // Location::kOutputOverlap; not applicable to other cases.
+      if (kind == HInstruction::kOr || kind == HInstruction::kXor) {
+        GenerateDataProcInstruction(kind,
+                                    out_lo,
+                                    first_lo,
+                                    ShifterOperand(second_lo, LSR, shift_value),
+                                    codegen);
+        GenerateDataProcInstruction(kind,
+                                    out_lo,
+                                    out_lo,
+                                    ShifterOperand(second_hi, LSL, 32 - shift_value),
+                                    codegen);
+        GenerateDataProcInstruction(kind,
+                                    out_hi,
+                                    first_hi,
+                                    ShifterOperand(second_hi, shift, shift_value),
+                                    codegen);
+      } else {
+        __ Lsr(IP, second_lo, shift_value);
+        __ orr(IP, IP, ShifterOperand(second_hi, LSL, 32 - shift_value));
+        GenerateDataProc(kind,
+                         out,
+                         first,
+                         ShifterOperand(IP),
+                         ShifterOperand(second_hi, shift, shift_value),
+                         codegen);
+      }
+    }
+  }
+}
+
+#undef __
+// NOLINT on __ macro to suppress wrong warning/fix (misc-macro-parentheses) from clang-tidy.
+#define __ down_cast<ArmAssembler*>(GetAssembler())->  // NOLINT
+
 void CodeGeneratorARM::DumpCoreRegister(std::ostream& stream, int reg) const {
   stream << Register(reg);
 }
@@ -6709,6 +6897,63 @@
   }
 }
 
+void LocationsBuilderARM::VisitDataProcWithShifterOp(
+    HDataProcWithShifterOp* instruction) {
+  DCHECK(instruction->GetType() == Primitive::kPrimInt ||
+         instruction->GetType() == Primitive::kPrimLong);
+  LocationSummary* locations =
+      new (GetGraph()->GetArena()) LocationSummary(instruction, LocationSummary::kNoCall);
+  const bool overlap = instruction->GetType() == Primitive::kPrimLong &&
+                       HDataProcWithShifterOp::IsExtensionOp(instruction->GetOpKind());
+
+  locations->SetInAt(0, Location::RequiresRegister());
+  locations->SetInAt(1, Location::RequiresRegister());
+  locations->SetOut(Location::RequiresRegister(),
+                    overlap ? Location::kOutputOverlap : Location::kNoOutputOverlap);
+}
+
+void InstructionCodeGeneratorARM::VisitDataProcWithShifterOp(
+    HDataProcWithShifterOp* instruction) {
+  const LocationSummary* const locations = instruction->GetLocations();
+  const HInstruction::InstructionKind kind = instruction->GetInstrKind();
+  const HDataProcWithShifterOp::OpKind op_kind = instruction->GetOpKind();
+  const Location left = locations->InAt(0);
+  const Location right = locations->InAt(1);
+  const Location out = locations->Out();
+
+  if (instruction->GetType() == Primitive::kPrimInt) {
+    DCHECK(!HDataProcWithShifterOp::IsExtensionOp(op_kind));
+
+    const Register second = instruction->InputAt(1)->GetType() == Primitive::kPrimLong
+        ? right.AsRegisterPairLow<Register>()
+        : right.AsRegister<Register>();
+
+    GenerateDataProcInstruction(kind,
+                                out.AsRegister<Register>(),
+                                left.AsRegister<Register>(),
+                                ShifterOperand(second,
+                                               ShiftFromOpKind(op_kind),
+                                               instruction->GetShiftAmount()),
+                                codegen_);
+  } else {
+    DCHECK_EQ(instruction->GetType(), Primitive::kPrimLong);
+
+    if (HDataProcWithShifterOp::IsExtensionOp(op_kind)) {
+      const Register second = right.AsRegister<Register>();
+
+      DCHECK_NE(out.AsRegisterPairLow<Register>(), second);
+      GenerateDataProc(kind,
+                       out,
+                       left,
+                       ShifterOperand(second),
+                       ShifterOperand(second, ASR, 31),
+                       codegen_);
+    } else {
+      GenerateLongDataProc(instruction, codegen_);
+    }
+  }
+}
+
 void InstructionCodeGeneratorARM::GenerateAndConst(Register out, Register first, uint32_t value) {
   // Optimize special cases for individual halfs of `and-long` (`and` is simplified earlier).
   if (value == 0xffffffffu) {
diff --git a/compiler/optimizing/code_generator_arm64.cc b/compiler/optimizing/code_generator_arm64.cc
index e6032d2..edccbd4 100644
--- a/compiler/optimizing/code_generator_arm64.cc
+++ b/compiler/optimizing/code_generator_arm64.cc
@@ -2277,8 +2277,8 @@
   }
 }
 
-void LocationsBuilderARM64::VisitArm64DataProcWithShifterOp(
-    HArm64DataProcWithShifterOp* instruction) {
+void LocationsBuilderARM64::VisitDataProcWithShifterOp(
+    HDataProcWithShifterOp* instruction) {
   DCHECK(instruction->GetType() == Primitive::kPrimInt ||
          instruction->GetType() == Primitive::kPrimLong);
   LocationSummary* locations =
@@ -2292,8 +2292,8 @@
   locations->SetOut(Location::RequiresRegister(), Location::kNoOutputOverlap);
 }
 
-void InstructionCodeGeneratorARM64::VisitArm64DataProcWithShifterOp(
-    HArm64DataProcWithShifterOp* instruction) {
+void InstructionCodeGeneratorARM64::VisitDataProcWithShifterOp(
+    HDataProcWithShifterOp* instruction) {
   Primitive::Type type = instruction->GetType();
   HInstruction::InstructionKind kind = instruction->GetInstrKind();
   DCHECK(type == Primitive::kPrimInt || type == Primitive::kPrimLong);
@@ -2302,21 +2302,20 @@
   if (kind != HInstruction::kNeg) {
     left = InputRegisterAt(instruction, 0);
   }
-  // If this `HArm64DataProcWithShifterOp` was created by merging a type conversion as the
+  // If this `HDataProcWithShifterOp` was created by merging a type conversion as the
   // shifter operand operation, the IR generating `right_reg` (input to the type
   // conversion) can have a different type from the current instruction's type,
   // so we manually indicate the type.
   Register right_reg = RegisterFrom(instruction->GetLocations()->InAt(1), type);
-  int64_t shift_amount = instruction->GetShiftAmount() &
-      (type == Primitive::kPrimInt ? kMaxIntShiftDistance : kMaxLongShiftDistance);
-
   Operand right_operand(0);
 
-  HArm64DataProcWithShifterOp::OpKind op_kind = instruction->GetOpKind();
-  if (HArm64DataProcWithShifterOp::IsExtensionOp(op_kind)) {
+  HDataProcWithShifterOp::OpKind op_kind = instruction->GetOpKind();
+  if (HDataProcWithShifterOp::IsExtensionOp(op_kind)) {
     right_operand = Operand(right_reg, helpers::ExtendFromOpKind(op_kind));
   } else {
-    right_operand = Operand(right_reg, helpers::ShiftFromOpKind(op_kind), shift_amount);
+    right_operand = Operand(right_reg,
+                            helpers::ShiftFromOpKind(op_kind),
+                            instruction->GetShiftAmount());
   }
 
   // Logical binary operations do not support extension operations in the
diff --git a/compiler/optimizing/code_generator_arm_vixl.cc b/compiler/optimizing/code_generator_arm_vixl.cc
index 5c4ca5b..6bfbe4a 100644
--- a/compiler/optimizing/code_generator_arm_vixl.cc
+++ b/compiler/optimizing/code_generator_arm_vixl.cc
@@ -1216,6 +1216,17 @@
   }
 }
 
+inline ShiftType ShiftFromOpKind(HDataProcWithShifterOp::OpKind op_kind) {
+  switch (op_kind) {
+    case HDataProcWithShifterOp::kASR: return ShiftType::ASR;
+    case HDataProcWithShifterOp::kLSL: return ShiftType::LSL;
+    case HDataProcWithShifterOp::kLSR: return ShiftType::LSR;
+    default:
+      LOG(FATAL) << "Unexpected op kind " << op_kind;
+      UNREACHABLE();
+  }
+}
+
 void CodeGeneratorARMVIXL::DumpCoreRegister(std::ostream& stream, int reg) const {
   stream << vixl32::Register(reg);
 }
@@ -1260,6 +1271,185 @@
   return 0;
 }
 
+static void GenerateDataProcInstruction(HInstruction::InstructionKind kind,
+                                        vixl32::Register out,
+                                        vixl32::Register first,
+                                        const Operand& second,
+                                        CodeGeneratorARMVIXL* codegen) {
+  if (second.IsImmediate() && second.GetImmediate() == 0) {
+    const Operand in = kind == HInstruction::kAnd
+        ? Operand(0)
+        : Operand(first);
+
+    __ Mov(out, in);
+  } else {
+    switch (kind) {
+      case HInstruction::kAdd:
+        __ Add(out, first, second);
+        break;
+      case HInstruction::kAnd:
+        __ And(out, first, second);
+        break;
+      case HInstruction::kOr:
+        __ Orr(out, first, second);
+        break;
+      case HInstruction::kSub:
+        __ Sub(out, first, second);
+        break;
+      case HInstruction::kXor:
+        __ Eor(out, first, second);
+        break;
+      default:
+        LOG(FATAL) << "Unexpected instruction kind: " << kind;
+        UNREACHABLE();
+    }
+  }
+}
+
+static void GenerateDataProc(HInstruction::InstructionKind kind,
+                             const Location& out,
+                             const Location& first,
+                             const Operand& second_lo,
+                             const Operand& second_hi,
+                             CodeGeneratorARMVIXL* codegen) {
+  const vixl32::Register first_hi = HighRegisterFrom(first);
+  const vixl32::Register first_lo = LowRegisterFrom(first);
+  const vixl32::Register out_hi = HighRegisterFrom(out);
+  const vixl32::Register out_lo = LowRegisterFrom(out);
+
+  if (kind == HInstruction::kAdd) {
+    __ Adds(out_lo, first_lo, second_lo);
+    __ Adc(out_hi, first_hi, second_hi);
+  } else if (kind == HInstruction::kSub) {
+    __ Subs(out_lo, first_lo, second_lo);
+    __ Sbc(out_hi, first_hi, second_hi);
+  } else {
+    GenerateDataProcInstruction(kind, out_lo, first_lo, second_lo, codegen);
+    GenerateDataProcInstruction(kind, out_hi, first_hi, second_hi, codegen);
+  }
+}
+
+static Operand GetShifterOperand(vixl32::Register rm, ShiftType shift, uint32_t shift_imm) {
+  return shift_imm == 0 ? Operand(rm) : Operand(rm, shift, shift_imm);
+}
+
+static void GenerateLongDataProc(HDataProcWithShifterOp* instruction,
+                                 CodeGeneratorARMVIXL* codegen) {
+  DCHECK_EQ(instruction->GetType(), Primitive::kPrimLong);
+  DCHECK(HDataProcWithShifterOp::IsShiftOp(instruction->GetOpKind()));
+
+  const LocationSummary* const locations = instruction->GetLocations();
+  const uint32_t shift_value = instruction->GetShiftAmount();
+  const HInstruction::InstructionKind kind = instruction->GetInstrKind();
+  const Location first = locations->InAt(0);
+  const Location second = locations->InAt(1);
+  const Location out = locations->Out();
+  const vixl32::Register first_hi = HighRegisterFrom(first);
+  const vixl32::Register first_lo = LowRegisterFrom(first);
+  const vixl32::Register out_hi = HighRegisterFrom(out);
+  const vixl32::Register out_lo = LowRegisterFrom(out);
+  const vixl32::Register second_hi = HighRegisterFrom(second);
+  const vixl32::Register second_lo = LowRegisterFrom(second);
+  const ShiftType shift = ShiftFromOpKind(instruction->GetOpKind());
+
+  if (shift_value >= 32) {
+    if (shift == ShiftType::LSL) {
+      GenerateDataProcInstruction(kind,
+                                  out_hi,
+                                  first_hi,
+                                  Operand(second_lo, ShiftType::LSL, shift_value - 32),
+                                  codegen);
+      GenerateDataProcInstruction(kind, out_lo, first_lo, 0, codegen);
+    } else if (shift == ShiftType::ASR) {
+      GenerateDataProc(kind,
+                       out,
+                       first,
+                       GetShifterOperand(second_hi, ShiftType::ASR, shift_value - 32),
+                       Operand(second_hi, ShiftType::ASR, 31),
+                       codegen);
+    } else {
+      DCHECK_EQ(shift, ShiftType::LSR);
+      GenerateDataProc(kind,
+                       out,
+                       first,
+                       GetShifterOperand(second_hi, ShiftType::LSR, shift_value - 32),
+                       0,
+                       codegen);
+    }
+  } else {
+    DCHECK_GT(shift_value, 1U);
+    DCHECK_LT(shift_value, 32U);
+
+    UseScratchRegisterScope temps(codegen->GetVIXLAssembler());
+
+    if (shift == ShiftType::LSL) {
+      // We are not doing this for HInstruction::kAdd because the output will require
+      // Location::kOutputOverlap; not applicable to other cases.
+      if (kind == HInstruction::kOr || kind == HInstruction::kXor) {
+        GenerateDataProcInstruction(kind,
+                                    out_hi,
+                                    first_hi,
+                                    Operand(second_hi, ShiftType::LSL, shift_value),
+                                    codegen);
+        GenerateDataProcInstruction(kind,
+                                    out_hi,
+                                    out_hi,
+                                    Operand(second_lo, ShiftType::LSR, 32 - shift_value),
+                                    codegen);
+        GenerateDataProcInstruction(kind,
+                                    out_lo,
+                                    first_lo,
+                                    Operand(second_lo, ShiftType::LSL, shift_value),
+                                    codegen);
+      } else {
+        const vixl32::Register temp = temps.Acquire();
+
+        __ Lsl(temp, second_hi, shift_value);
+        __ Orr(temp, temp, Operand(second_lo, ShiftType::LSR, 32 - shift_value));
+        GenerateDataProc(kind,
+                         out,
+                         first,
+                         Operand(second_lo, ShiftType::LSL, shift_value),
+                         temp,
+                         codegen);
+      }
+    } else {
+      DCHECK(shift == ShiftType::ASR || shift == ShiftType::LSR);
+
+      // We are not doing this for HInstruction::kAdd because the output will require
+      // Location::kOutputOverlap; not applicable to other cases.
+      if (kind == HInstruction::kOr || kind == HInstruction::kXor) {
+        GenerateDataProcInstruction(kind,
+                                    out_lo,
+                                    first_lo,
+                                    Operand(second_lo, ShiftType::LSR, shift_value),
+                                    codegen);
+        GenerateDataProcInstruction(kind,
+                                    out_lo,
+                                    out_lo,
+                                    Operand(second_hi, ShiftType::LSL, 32 - shift_value),
+                                    codegen);
+        GenerateDataProcInstruction(kind,
+                                    out_hi,
+                                    first_hi,
+                                    Operand(second_hi, shift, shift_value),
+                                    codegen);
+      } else {
+        const vixl32::Register temp = temps.Acquire();
+
+        __ Lsr(temp, second_lo, shift_value);
+        __ Orr(temp, temp, Operand(second_hi, ShiftType::LSL, 32 - shift_value));
+        GenerateDataProc(kind,
+                         out,
+                         first,
+                         temp,
+                         Operand(second_hi, shift, shift_value),
+                         codegen);
+      }
+    }
+  }
+}
+
 #undef __
 
 CodeGeneratorARMVIXL::CodeGeneratorARMVIXL(HGraph* graph,
@@ -6781,6 +6971,60 @@
   }
 }
 
+void LocationsBuilderARMVIXL::VisitDataProcWithShifterOp(
+    HDataProcWithShifterOp* instruction) {
+  DCHECK(instruction->GetType() == Primitive::kPrimInt ||
+         instruction->GetType() == Primitive::kPrimLong);
+  LocationSummary* locations =
+      new (GetGraph()->GetArena()) LocationSummary(instruction, LocationSummary::kNoCall);
+  const bool overlap = instruction->GetType() == Primitive::kPrimLong &&
+                       HDataProcWithShifterOp::IsExtensionOp(instruction->GetOpKind());
+
+  locations->SetInAt(0, Location::RequiresRegister());
+  locations->SetInAt(1, Location::RequiresRegister());
+  locations->SetOut(Location::RequiresRegister(),
+                    overlap ? Location::kOutputOverlap : Location::kNoOutputOverlap);
+}
+
+void InstructionCodeGeneratorARMVIXL::VisitDataProcWithShifterOp(
+    HDataProcWithShifterOp* instruction) {
+  const LocationSummary* const locations = instruction->GetLocations();
+  const HInstruction::InstructionKind kind = instruction->GetInstrKind();
+  const HDataProcWithShifterOp::OpKind op_kind = instruction->GetOpKind();
+
+  if (instruction->GetType() == Primitive::kPrimInt) {
+    DCHECK(!HDataProcWithShifterOp::IsExtensionOp(op_kind));
+
+    const vixl32::Register second = instruction->InputAt(1)->GetType() == Primitive::kPrimLong
+        ? LowRegisterFrom(locations->InAt(1))
+        : InputRegisterAt(instruction, 1);
+
+    GenerateDataProcInstruction(kind,
+                                OutputRegister(instruction),
+                                InputRegisterAt(instruction, 0),
+                                Operand(second,
+                                        ShiftFromOpKind(op_kind),
+                                        instruction->GetShiftAmount()),
+                                codegen_);
+  } else {
+    DCHECK_EQ(instruction->GetType(), Primitive::kPrimLong);
+
+    if (HDataProcWithShifterOp::IsExtensionOp(op_kind)) {
+      const vixl32::Register second = InputRegisterAt(instruction, 1);
+
+      DCHECK(!LowRegisterFrom(locations->Out()).Is(second));
+      GenerateDataProc(kind,
+                       locations->Out(),
+                       locations->InAt(0),
+                       second,
+                       Operand(second, ShiftType::ASR, 31),
+                       codegen_);
+    } else {
+      GenerateLongDataProc(instruction, codegen_);
+    }
+  }
+}
+
 // TODO(VIXL): Remove optimizations in the helper when they are implemented in vixl.
 void InstructionCodeGeneratorARMVIXL::GenerateAndConst(vixl32::Register out,
                                                        vixl32::Register first,
diff --git a/compiler/optimizing/code_generator_arm_vixl.h b/compiler/optimizing/code_generator_arm_vixl.h
index 8ae3b7d..3f52c72 100644
--- a/compiler/optimizing/code_generator_arm_vixl.h
+++ b/compiler/optimizing/code_generator_arm_vixl.h
@@ -35,11 +35,11 @@
 #include "aarch32/macro-assembler-aarch32.h"
 #pragma GCC diagnostic pop
 
-// True if VIXL32 should be used for codegen on ARM.
-#ifdef ART_USE_VIXL_ARM_BACKEND
-static constexpr bool kArmUseVIXL32 = true;
-#else
+// Default to use the VIXL-based backend on ARM.
+#ifdef ART_USE_OLD_ARM_BACKEND
 static constexpr bool kArmUseVIXL32 = false;
+#else
+static constexpr bool kArmUseVIXL32 = true;
 #endif
 
 namespace art {
diff --git a/compiler/optimizing/common_arm.h b/compiler/optimizing/common_arm.h
index ecb8687..e184745 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 "instruction_simplifier_shared.h"
 #include "debug/dwarf/register.h"
 #include "locations.h"
 #include "nodes.h"
@@ -29,6 +30,9 @@
 #pragma GCC diagnostic pop
 
 namespace art {
+
+using helpers::HasShifterOperand;
+
 namespace arm {
 namespace helpers {
 
@@ -218,6 +222,14 @@
   return Location::FpuRegisterPairLocation(low.GetCode(), high.GetCode());
 }
 
+inline bool ShifterOperandSupportsExtension(HInstruction* instruction) {
+  DCHECK(HasShifterOperand(instruction, kArm));
+  // TODO: HAdd applied to the other integral types could make use of
+  // the SXTAB, SXTAH, UXTAB and UXTAH instructions.
+  return instruction->GetType() == Primitive::kPrimLong &&
+         (instruction->IsAdd() || instruction->IsSub());
+}
+
 }  // namespace helpers
 }  // namespace arm
 }  // namespace art
diff --git a/compiler/optimizing/common_arm64.h b/compiler/optimizing/common_arm64.h
index 93ea090..d3f431e 100644
--- a/compiler/optimizing/common_arm64.h
+++ b/compiler/optimizing/common_arm64.h
@@ -18,6 +18,7 @@
 #define ART_COMPILER_OPTIMIZING_COMMON_ARM64_H_
 
 #include "code_generator.h"
+#include "instruction_simplifier_shared.h"
 #include "locations.h"
 #include "nodes.h"
 #include "utils/arm64/assembler_arm64.h"
@@ -31,6 +32,10 @@
 #pragma GCC diagnostic pop
 
 namespace art {
+
+using helpers::CanFitInShifterOperand;
+using helpers::HasShifterOperand;
+
 namespace arm64 {
 namespace helpers {
 
@@ -290,11 +295,11 @@
   return true;
 }
 
-inline vixl::aarch64::Shift ShiftFromOpKind(HArm64DataProcWithShifterOp::OpKind op_kind) {
+inline vixl::aarch64::Shift ShiftFromOpKind(HDataProcWithShifterOp::OpKind op_kind) {
   switch (op_kind) {
-    case HArm64DataProcWithShifterOp::kASR: return vixl::aarch64::ASR;
-    case HArm64DataProcWithShifterOp::kLSL: return vixl::aarch64::LSL;
-    case HArm64DataProcWithShifterOp::kLSR: return vixl::aarch64::LSR;
+    case HDataProcWithShifterOp::kASR: return vixl::aarch64::ASR;
+    case HDataProcWithShifterOp::kLSL: return vixl::aarch64::LSL;
+    case HDataProcWithShifterOp::kLSR: return vixl::aarch64::LSR;
     default:
       LOG(FATAL) << "Unexpected op kind " << op_kind;
       UNREACHABLE();
@@ -302,14 +307,14 @@
   }
 }
 
-inline vixl::aarch64::Extend ExtendFromOpKind(HArm64DataProcWithShifterOp::OpKind op_kind) {
+inline vixl::aarch64::Extend ExtendFromOpKind(HDataProcWithShifterOp::OpKind op_kind) {
   switch (op_kind) {
-    case HArm64DataProcWithShifterOp::kUXTB: return vixl::aarch64::UXTB;
-    case HArm64DataProcWithShifterOp::kUXTH: return vixl::aarch64::UXTH;
-    case HArm64DataProcWithShifterOp::kUXTW: return vixl::aarch64::UXTW;
-    case HArm64DataProcWithShifterOp::kSXTB: return vixl::aarch64::SXTB;
-    case HArm64DataProcWithShifterOp::kSXTH: return vixl::aarch64::SXTH;
-    case HArm64DataProcWithShifterOp::kSXTW: return vixl::aarch64::SXTW;
+    case HDataProcWithShifterOp::kUXTB: return vixl::aarch64::UXTB;
+    case HDataProcWithShifterOp::kUXTH: return vixl::aarch64::UXTH;
+    case HDataProcWithShifterOp::kUXTW: return vixl::aarch64::UXTW;
+    case HDataProcWithShifterOp::kSXTB: return vixl::aarch64::SXTB;
+    case HDataProcWithShifterOp::kSXTH: return vixl::aarch64::SXTH;
+    case HDataProcWithShifterOp::kSXTW: return vixl::aarch64::SXTW;
     default:
       LOG(FATAL) << "Unexpected op kind " << op_kind;
       UNREACHABLE();
@@ -317,31 +322,8 @@
   }
 }
 
-inline bool CanFitInShifterOperand(HInstruction* instruction) {
-  if (instruction->IsTypeConversion()) {
-    HTypeConversion* conversion = instruction->AsTypeConversion();
-    Primitive::Type result_type = conversion->GetResultType();
-    Primitive::Type input_type = conversion->GetInputType();
-    // We don't expect to see the same type as input and result.
-    return Primitive::IsIntegralType(result_type) && Primitive::IsIntegralType(input_type) &&
-        (result_type != input_type);
-  } else {
-    return (instruction->IsShl() && instruction->AsShl()->InputAt(1)->IsIntConstant()) ||
-        (instruction->IsShr() && instruction->AsShr()->InputAt(1)->IsIntConstant()) ||
-        (instruction->IsUShr() && instruction->AsUShr()->InputAt(1)->IsIntConstant());
-  }
-}
-
-inline bool HasShifterOperand(HInstruction* instr) {
-  // `neg` instructions are an alias of `sub` using the zero register as the
-  // first register input.
-  bool res = instr->IsAdd() || instr->IsAnd() || instr->IsNeg() ||
-      instr->IsOr() || instr->IsSub() || instr->IsXor();
-  return res;
-}
-
 inline bool ShifterOperandSupportsExtension(HInstruction* instruction) {
-  DCHECK(HasShifterOperand(instruction));
+  DCHECK(HasShifterOperand(instruction, kArm64));
   // Although the `neg` instruction is an alias of the `sub` instruction, `HNeg`
   // does *not* support extension. This is because the `extended register` form
   // of the `sub` instruction interprets the left register with code 31 as the
diff --git a/compiler/optimizing/dex_cache_array_fixups_arm.cc b/compiler/optimizing/dex_cache_array_fixups_arm.cc
index cfcb276..0c832a5 100644
--- a/compiler/optimizing/dex_cache_array_fixups_arm.cc
+++ b/compiler/optimizing/dex_cache_array_fixups_arm.cc
@@ -17,23 +17,23 @@
 #include "dex_cache_array_fixups_arm.h"
 
 #include "base/arena_containers.h"
-#ifdef ART_USE_VIXL_ARM_BACKEND
-#include "code_generator_arm_vixl.h"
-#include "intrinsics_arm_vixl.h"
-#else
+#ifdef ART_USE_OLD_ARM_BACKEND
 #include "code_generator_arm.h"
 #include "intrinsics_arm.h"
+#else
+#include "code_generator_arm_vixl.h"
+#include "intrinsics_arm_vixl.h"
 #endif
 #include "utils/dex_cache_arrays_layout-inl.h"
 
 namespace art {
 namespace arm {
-#ifdef ART_USE_VIXL_ARM_BACKEND
-typedef CodeGeneratorARMVIXL CodeGeneratorARMType;
-typedef IntrinsicLocationsBuilderARMVIXL IntrinsicLocationsBuilderARMType;
-#else
+#ifdef ART_USE_OLD_ARM_BACKEND
 typedef CodeGeneratorARM CodeGeneratorARMType;
 typedef IntrinsicLocationsBuilderARM IntrinsicLocationsBuilderARMType;
+#else
+typedef CodeGeneratorARMVIXL CodeGeneratorARMType;
+typedef IntrinsicLocationsBuilderARMVIXL IntrinsicLocationsBuilderARMType;
 #endif
 
 /**
diff --git a/compiler/optimizing/graph_visualizer.cc b/compiler/optimizing/graph_visualizer.cc
index f6fba88..2bf5c53 100644
--- a/compiler/optimizing/graph_visualizer.cc
+++ b/compiler/optimizing/graph_visualizer.cc
@@ -511,12 +511,10 @@
   void VisitBitwiseNegatedRight(HBitwiseNegatedRight* instruction) OVERRIDE {
     StartAttributeStream("kind") << instruction->GetOpKind();
   }
-#endif
 
-#ifdef ART_ENABLE_CODEGEN_arm64
-  void VisitArm64DataProcWithShifterOp(HArm64DataProcWithShifterOp* instruction) OVERRIDE {
+  void VisitDataProcWithShifterOp(HDataProcWithShifterOp* instruction) OVERRIDE {
     StartAttributeStream("kind") << instruction->GetInstrKind() << "+" << instruction->GetOpKind();
-    if (HArm64DataProcWithShifterOp::IsShiftOp(instruction->GetOpKind())) {
+    if (HDataProcWithShifterOp::IsShiftOp(instruction->GetOpKind())) {
       StartAttributeStream("shift") << instruction->GetShiftAmount();
     }
   }
diff --git a/compiler/optimizing/inliner.cc b/compiler/optimizing/inliner.cc
index e012a42..8c73f1d 100644
--- a/compiler/optimizing/inliner.cc
+++ b/compiler/optimizing/inliner.cc
@@ -192,9 +192,9 @@
 }
 
 static dex::TypeIndex FindClassIndexIn(mirror::Class* cls,
-                                       const DexFile& dex_file,
-                                       Handle<mirror::DexCache> dex_cache)
+                                       const DexCompilationUnit& compilation_unit)
     REQUIRES_SHARED(Locks::mutator_lock_) {
+  const DexFile& dex_file = *compilation_unit.GetDexFile();
   dex::TypeIndex index;
   if (cls->GetDexCache() == nullptr) {
     DCHECK(cls->IsArrayClass()) << cls->PrettyClass();
@@ -203,22 +203,19 @@
     DCHECK(cls->IsProxyClass()) << cls->PrettyClass();
     // TODO: deal with proxy classes.
   } else if (IsSameDexFile(cls->GetDexFile(), dex_file)) {
-    DCHECK_EQ(cls->GetDexCache(), dex_cache.Get());
+    DCHECK_EQ(cls->GetDexCache(), compilation_unit.GetDexCache().Get());
     index = cls->GetDexTypeIndex();
-    // Update the dex cache to ensure the class is in. The generated code will
-    // consider it is. We make it safe by updating the dex cache, as other
-    // dex files might also load the class, and there is no guarantee the dex
-    // cache of the dex file of the class will be updated.
-    if (dex_cache->GetResolvedType(index) == nullptr) {
-      dex_cache->SetResolvedType(index, cls);
-    }
   } else {
     index = cls->FindTypeIndexInOtherDexFile(dex_file);
-    // We cannot guarantee the entry in the dex cache will resolve to the same class,
+    // We cannot guarantee the entry will resolve to the same class,
     // as there may be different class loaders. So only return the index if it's
-    // the right class in the dex cache already.
-    if (index.IsValid() && dex_cache->GetResolvedType(index) != cls) {
-      index = dex::TypeIndex::Invalid();
+    // the right class already resolved with the class loader.
+    if (index.IsValid()) {
+      ObjPtr<mirror::Class> resolved = ClassLinker::LookupResolvedType(
+          index, compilation_unit.GetDexCache().Get(), compilation_unit.GetClassLoader().Get());
+      if (resolved != cls) {
+        index = dex::TypeIndex::Invalid();
+      }
     }
   }
 
@@ -445,9 +442,8 @@
   DCHECK(invoke_instruction->IsInvokeVirtual() || invoke_instruction->IsInvokeInterface())
       << invoke_instruction->DebugName();
 
-  const DexFile& caller_dex_file = *caller_compilation_unit_.GetDexFile();
   dex::TypeIndex class_index = FindClassIndexIn(
-      GetMonomorphicType(classes), caller_dex_file, caller_compilation_unit_.GetDexCache());
+      GetMonomorphicType(classes), caller_compilation_unit_);
   if (!class_index.IsValid()) {
     VLOG(compiler) << "Call to " << ArtMethod::PrettyMethod(resolved_method)
                    << " from inline cache is not inlined because its class is not"
@@ -490,6 +486,7 @@
   // 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(),
                                      handles_,
                                      /* is_first_run */ false);
@@ -583,7 +580,6 @@
 
   ClassLinker* class_linker = caller_compilation_unit_.GetClassLinker();
   PointerSize pointer_size = class_linker->GetImagePointerSize();
-  const DexFile& caller_dex_file = *caller_compilation_unit_.GetDexFile();
 
   bool all_targets_inlined = true;
   bool one_target_inlined = false;
@@ -605,8 +601,7 @@
     HInstruction* cursor = invoke_instruction->GetPrevious();
     HBasicBlock* bb_cursor = invoke_instruction->GetBlock();
 
-    dex::TypeIndex class_index = FindClassIndexIn(
-        handle.Get(), caller_dex_file, caller_compilation_unit_.GetDexCache());
+    dex::TypeIndex class_index = FindClassIndexIn(handle.Get(), caller_compilation_unit_);
     HInstruction* return_replacement = nullptr;
     if (!class_index.IsValid() ||
         !TryBuildAndInline(invoke_instruction,
@@ -662,6 +657,7 @@
 
   // Run type propagation to get the guards typed.
   ReferenceTypePropagation rtp_fixup(graph_,
+                                     outer_compilation_unit_.GetClassLoader(),
                                      outer_compilation_unit_.GetDexCache(),
                                      handles_,
                                      /* is_first_run */ false);
@@ -855,6 +851,7 @@
 
   // Run type propagation to get the guard typed.
   ReferenceTypePropagation rtp_fixup(graph_,
+                                     outer_compilation_unit_.GetClassLoader(),
                                      outer_compilation_unit_.GetDexCache(),
                                      handles_,
                                      /* is_first_run */ false);
@@ -923,6 +920,7 @@
     // 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(),
                              handles_,
                              /* is_first_run */ false).Run();
@@ -1175,7 +1173,11 @@
       /* dex_pc */ 0);
   if (iget->GetType() == Primitive::kPrimNot) {
     // Use the same dex_cache that we used for field lookup as the hint_dex_cache.
-    ReferenceTypePropagation rtp(graph_, dex_cache, handles_, /* is_first_run */ false);
+    ReferenceTypePropagation rtp(graph_,
+                                 outer_compilation_unit_.GetClassLoader(),
+                                 dex_cache,
+                                 handles_,
+                                 /* is_first_run */ false);
     rtp.Visit(iget);
   }
   return iget;
@@ -1221,7 +1223,7 @@
       resolved_method->GetDeclaringClass()->GetClassLoader()));
 
   DexCompilationUnit dex_compilation_unit(
-      class_loader.ToJObject(),
+      class_loader,
       class_linker,
       callee_dex_file,
       code_item,
@@ -1338,6 +1340,7 @@
   // 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(),
                              handles_,
                              /* is_first_run */ false).Run();
diff --git a/compiler/optimizing/instruction_builder.cc b/compiler/optimizing/instruction_builder.cc
index 3374e42..c60f6e5 100644
--- a/compiler/optimizing/instruction_builder.cc
+++ b/compiler/optimizing/instruction_builder.cc
@@ -669,11 +669,10 @@
 
 ArtMethod* HInstructionBuilder::ResolveMethod(uint16_t method_idx, InvokeType invoke_type) {
   ScopedObjectAccess soa(Thread::Current());
-  StackHandleScope<3> hs(soa.Self());
+  StackHandleScope<2> hs(soa.Self());
 
   ClassLinker* class_linker = dex_compilation_unit_->GetClassLinker();
-  Handle<mirror::ClassLoader> class_loader(hs.NewHandle(
-      soa.Decode<mirror::ClassLoader>(dex_compilation_unit_->GetClassLoader())));
+  Handle<mirror::ClassLoader> class_loader = dex_compilation_unit_->GetClassLoader();
   Handle<mirror::Class> compiling_class(hs.NewHandle(GetCompilingClass()));
   // We fetch the referenced class eagerly (that is, the class pointed by in the MethodId
   // at method_idx), as `CanAccessResolvedMethod` expects it be be in the dex cache.
@@ -1260,9 +1259,7 @@
 static mirror::Class* GetClassFrom(CompilerDriver* driver,
                                    const DexCompilationUnit& compilation_unit) {
   ScopedObjectAccess soa(Thread::Current());
-  StackHandleScope<1> hs(soa.Self());
-  Handle<mirror::ClassLoader> class_loader(hs.NewHandle(
-      soa.Decode<mirror::ClassLoader>(compilation_unit.GetClassLoader())));
+  Handle<mirror::ClassLoader> class_loader = compilation_unit.GetClassLoader();
   Handle<mirror::DexCache> dex_cache = compilation_unit.GetDexCache();
 
   return driver->ResolveCompilingMethodsClass(soa, dex_cache, class_loader, &compilation_unit);
@@ -1278,10 +1275,9 @@
 
 bool HInstructionBuilder::IsOutermostCompilingClass(dex::TypeIndex type_index) const {
   ScopedObjectAccess soa(Thread::Current());
-  StackHandleScope<3> hs(soa.Self());
+  StackHandleScope<2> hs(soa.Self());
   Handle<mirror::DexCache> dex_cache = dex_compilation_unit_->GetDexCache();
-  Handle<mirror::ClassLoader> class_loader(hs.NewHandle(
-      soa.Decode<mirror::ClassLoader>(dex_compilation_unit_->GetClassLoader())));
+  Handle<mirror::ClassLoader> class_loader = dex_compilation_unit_->GetClassLoader();
   Handle<mirror::Class> cls(hs.NewHandle(compiler_driver_->ResolveClass(
       soa, dex_cache, class_loader, type_index, dex_compilation_unit_)));
   Handle<mirror::Class> outer_class(hs.NewHandle(GetOutermostCompilingClass()));
@@ -1317,8 +1313,7 @@
   StackHandleScope<2> hs(soa.Self());
 
   ClassLinker* class_linker = dex_compilation_unit_->GetClassLinker();
-  Handle<mirror::ClassLoader> class_loader(hs.NewHandle(
-      soa.Decode<mirror::ClassLoader>(dex_compilation_unit_->GetClassLoader())));
+  Handle<mirror::ClassLoader> class_loader = dex_compilation_unit_->GetClassLoader();
   Handle<mirror::Class> compiling_class(hs.NewHandle(GetCompilingClass()));
 
   ArtField* resolved_field = class_linker->ResolveField(*dex_compilation_unit_->GetDexFile(),
@@ -1635,10 +1630,8 @@
 
 HLoadClass* HInstructionBuilder::BuildLoadClass(dex::TypeIndex type_index, uint32_t dex_pc) {
   ScopedObjectAccess soa(Thread::Current());
-  StackHandleScope<2> hs(soa.Self());
   const DexFile& dex_file = *dex_compilation_unit_->GetDexFile();
-  Handle<mirror::ClassLoader> class_loader(hs.NewHandle(
-      soa.Decode<mirror::ClassLoader>(dex_compilation_unit_->GetClassLoader())));
+  Handle<mirror::ClassLoader> class_loader = dex_compilation_unit_->GetClassLoader();
   Handle<mirror::Class> klass = handles_->NewHandle(compiler_driver_->ResolveClass(
       soa, dex_compilation_unit_->GetDexCache(), class_loader, type_index, dex_compilation_unit_));
 
@@ -1722,17 +1715,9 @@
   }
 }
 
-bool HInstructionBuilder::NeedsAccessCheck(dex::TypeIndex type_index,
-                                           Handle<mirror::DexCache> dex_cache,
-                                           bool* finalizable) const {
-  return !compiler_driver_->CanAccessInstantiableTypeWithoutChecks(
-      dex_compilation_unit_->GetDexMethodIndex(), dex_cache, type_index, finalizable);
-}
-
 bool HInstructionBuilder::NeedsAccessCheck(dex::TypeIndex type_index, bool* finalizable) const {
-  ScopedObjectAccess soa(Thread::Current());
-  Handle<mirror::DexCache> dex_cache = dex_compilation_unit_->GetDexCache();
-  return NeedsAccessCheck(type_index, dex_cache, finalizable);
+  return !compiler_driver_->CanAccessInstantiableTypeWithoutChecks(
+      LookupReferrerClass(), LookupResolvedType(type_index, *dex_compilation_unit_), finalizable);
 }
 
 bool HInstructionBuilder::CanDecodeQuickenedInfo() const {
@@ -2772,4 +2757,18 @@
   return true;
 }  // NOLINT(readability/fn_size)
 
+ObjPtr<mirror::Class> HInstructionBuilder::LookupResolvedType(
+    dex::TypeIndex type_index,
+    const DexCompilationUnit& compilation_unit) const {
+  return ClassLinker::LookupResolvedType(
+        type_index, compilation_unit.GetDexCache().Get(), compilation_unit.GetClassLoader().Get());
+}
+
+ObjPtr<mirror::Class> HInstructionBuilder::LookupReferrerClass() const {
+  // TODO: Cache the result in a Handle<mirror::Class>.
+  const DexFile::MethodId& method_id =
+      dex_compilation_unit_->GetDexFile()->GetMethodId(dex_compilation_unit_->GetDexMethodIndex());
+  return LookupResolvedType(method_id.class_idx_, *dex_compilation_unit_);
+}
+
 }  // namespace art
diff --git a/compiler/optimizing/instruction_builder.h b/compiler/optimizing/instruction_builder.h
index 3bb680c..e735a0c 100644
--- a/compiler/optimizing/instruction_builder.h
+++ b/compiler/optimizing/instruction_builder.h
@@ -106,11 +106,8 @@
 
   // Returns whether the current method needs access check for the type.
   // Output parameter finalizable is set to whether the type is finalizable.
-  bool NeedsAccessCheck(dex::TypeIndex type_index,
-                        Handle<mirror::DexCache> dex_cache,
-                        /*out*/bool* finalizable) const
+  bool NeedsAccessCheck(dex::TypeIndex type_index, /*out*/bool* finalizable) const
       REQUIRES_SHARED(Locks::mutator_lock_);
-  bool NeedsAccessCheck(dex::TypeIndex type_index, /*out*/bool* finalizable) const;
 
   template<typename T>
   void Unop_12x(const Instruction& instruction, Primitive::Type type, uint32_t dex_pc);
@@ -300,6 +297,12 @@
   // be found.
   ArtField* ResolveField(uint16_t field_idx, bool is_static, bool is_put);
 
+  ObjPtr<mirror::Class> LookupResolvedType(dex::TypeIndex type_index,
+                                           const DexCompilationUnit& compilation_unit) const
+      REQUIRES_SHARED(Locks::mutator_lock_);
+
+  ObjPtr<mirror::Class> LookupReferrerClass() const REQUIRES_SHARED(Locks::mutator_lock_);
+
   ArenaAllocator* const arena_;
   HGraph* const graph_;
   VariableSizedHandleScope* handles_;
diff --git a/compiler/optimizing/instruction_simplifier_arm.cc b/compiler/optimizing/instruction_simplifier_arm.cc
index 56e4c7a..5f5e29b 100644
--- a/compiler/optimizing/instruction_simplifier_arm.cc
+++ b/compiler/optimizing/instruction_simplifier_arm.cc
@@ -15,23 +15,124 @@
  */
 
 #include "code_generator.h"
+#include "common_arm.h"
 #include "instruction_simplifier_arm.h"
 #include "instruction_simplifier_shared.h"
 #include "mirror/array-inl.h"
+#include "nodes.h"
 
 namespace art {
+
+using helpers::CanFitInShifterOperand;
+using helpers::HasShifterOperand;
+
 namespace arm {
 
-void InstructionSimplifierArmVisitor::VisitMul(HMul* instruction) {
-  if (TryCombineMultiplyAccumulate(instruction, kArm)) {
+using helpers::ShifterOperandSupportsExtension;
+
+bool InstructionSimplifierArmVisitor::TryMergeIntoShifterOperand(HInstruction* use,
+                                                                 HInstruction* bitfield_op,
+                                                                 bool do_merge) {
+  DCHECK(HasShifterOperand(use, kArm));
+  DCHECK(use->IsBinaryOperation());
+  DCHECK(CanFitInShifterOperand(bitfield_op));
+  DCHECK(!bitfield_op->HasEnvironmentUses());
+
+  Primitive::Type type = use->GetType();
+  if (type != Primitive::kPrimInt && type != Primitive::kPrimLong) {
+    return false;
+  }
+
+  HInstruction* left = use->InputAt(0);
+  HInstruction* right = use->InputAt(1);
+  DCHECK(left == bitfield_op || right == bitfield_op);
+
+  if (left == right) {
+    // TODO: Handle special transformations in this situation?
+    // For example should we transform `(x << 1) + (x << 1)` into `(x << 2)`?
+    // Or should this be part of a separate transformation logic?
+    return false;
+  }
+
+  bool is_commutative = use->AsBinaryOperation()->IsCommutative();
+  HInstruction* other_input;
+  if (bitfield_op == right) {
+    other_input = left;
+  } else {
+    if (is_commutative) {
+      other_input = right;
+    } else {
+      return false;
+    }
+  }
+
+  HDataProcWithShifterOp::OpKind op_kind;
+  int shift_amount = 0;
+
+  HDataProcWithShifterOp::GetOpInfoFromInstruction(bitfield_op, &op_kind, &shift_amount);
+  shift_amount &= use->GetType() == Primitive::kPrimInt
+      ? kMaxIntShiftDistance
+      : kMaxLongShiftDistance;
+
+  if (HDataProcWithShifterOp::IsExtensionOp(op_kind)) {
+    if (!ShifterOperandSupportsExtension(use)) {
+      return false;
+    }
+  // Shift by 1 is a special case that results in the same number and type of instructions
+  // as this simplification, but potentially shorter code.
+  } else if (type == Primitive::kPrimLong && shift_amount == 1) {
+    return false;
+  }
+
+  if (do_merge) {
+    HDataProcWithShifterOp* alu_with_op =
+        new (GetGraph()->GetArena()) HDataProcWithShifterOp(use,
+                                                            other_input,
+                                                            bitfield_op->InputAt(0),
+                                                            op_kind,
+                                                            shift_amount,
+                                                            use->GetDexPc());
+    use->GetBlock()->ReplaceAndRemoveInstructionWith(use, alu_with_op);
+    if (bitfield_op->GetUses().empty()) {
+      bitfield_op->GetBlock()->RemoveInstruction(bitfield_op);
+    }
     RecordSimplification();
   }
+
+  return true;
 }
 
-void InstructionSimplifierArmVisitor::VisitOr(HOr* instruction) {
-  if (TryMergeNegatedInput(instruction)) {
-    RecordSimplification();
+// Merge a bitfield move instruction into its uses if it can be merged in all of them.
+bool InstructionSimplifierArmVisitor::TryMergeIntoUsersShifterOperand(HInstruction* bitfield_op) {
+  DCHECK(CanFitInShifterOperand(bitfield_op));
+
+  if (bitfield_op->HasEnvironmentUses()) {
+    return false;
   }
+
+  const HUseList<HInstruction*>& uses = bitfield_op->GetUses();
+
+  // Check whether we can merge the instruction in all its users' shifter operand.
+  for (const HUseListNode<HInstruction*>& use : uses) {
+    HInstruction* user = use.GetUser();
+    if (!HasShifterOperand(user, kArm)) {
+      return false;
+    }
+    if (!CanMergeIntoShifterOperand(user, bitfield_op)) {
+      return false;
+    }
+  }
+
+  // Merge the instruction into its uses.
+  for (auto it = uses.begin(), end = uses.end(); it != end; /* ++it below */) {
+    HInstruction* user = it->GetUser();
+    // Increment `it` now because `*it` will disappear thanks to MergeIntoShifterOperand().
+    ++it;
+    bool merged = MergeIntoShifterOperand(user, bitfield_op);
+    DCHECK(merged);
+  }
+
+  return true;
 }
 
 void InstructionSimplifierArmVisitor::VisitAnd(HAnd* instruction) {
@@ -89,5 +190,49 @@
   }
 }
 
+void InstructionSimplifierArmVisitor::VisitMul(HMul* instruction) {
+  if (TryCombineMultiplyAccumulate(instruction, kArm)) {
+    RecordSimplification();
+  }
+}
+
+void InstructionSimplifierArmVisitor::VisitOr(HOr* instruction) {
+  if (TryMergeNegatedInput(instruction)) {
+    RecordSimplification();
+  }
+}
+
+void InstructionSimplifierArmVisitor::VisitShl(HShl* instruction) {
+  if (instruction->InputAt(1)->IsConstant()) {
+    TryMergeIntoUsersShifterOperand(instruction);
+  }
+}
+
+void InstructionSimplifierArmVisitor::VisitShr(HShr* instruction) {
+  if (instruction->InputAt(1)->IsConstant()) {
+    TryMergeIntoUsersShifterOperand(instruction);
+  }
+}
+
+void InstructionSimplifierArmVisitor::VisitTypeConversion(HTypeConversion* instruction) {
+  Primitive::Type result_type = instruction->GetResultType();
+  Primitive::Type input_type = instruction->GetInputType();
+
+  if (input_type == result_type) {
+    // We let the arch-independent code handle this.
+    return;
+  }
+
+  if (Primitive::IsIntegralType(result_type) && Primitive::IsIntegralType(input_type)) {
+    TryMergeIntoUsersShifterOperand(instruction);
+  }
+}
+
+void InstructionSimplifierArmVisitor::VisitUShr(HUShr* instruction) {
+  if (instruction->InputAt(1)->IsConstant()) {
+    TryMergeIntoUsersShifterOperand(instruction);
+  }
+}
+
 }  // namespace arm
 }  // namespace art
diff --git a/compiler/optimizing/instruction_simplifier_arm.h b/compiler/optimizing/instruction_simplifier_arm.h
index 9b54511..e2ed257 100644
--- a/compiler/optimizing/instruction_simplifier_arm.h
+++ b/compiler/optimizing/instruction_simplifier_arm.h
@@ -35,11 +35,41 @@
     }
   }
 
-  void VisitMul(HMul* instruction) OVERRIDE;
-  void VisitOr(HOr* instruction) OVERRIDE;
+  bool TryMergeIntoUsersShifterOperand(HInstruction* instruction);
+  bool TryMergeIntoShifterOperand(HInstruction* use, HInstruction* bitfield_op, bool do_merge);
+  bool CanMergeIntoShifterOperand(HInstruction* use, HInstruction* bitfield_op) {
+    return TryMergeIntoShifterOperand(use, bitfield_op, /* do_merge */ false);
+  }
+  bool MergeIntoShifterOperand(HInstruction* use, HInstruction* bitfield_op) {
+    DCHECK(CanMergeIntoShifterOperand(use, bitfield_op));
+    return TryMergeIntoShifterOperand(use, bitfield_op, /* do_merge */ true);
+  }
+
+  /**
+   * This simplifier uses a special-purpose BB visitor.
+   * (1) No need to visit Phi nodes.
+   * (2) Since statements can be removed in a "forward" fashion,
+   *     the visitor should test if each statement is still there.
+   */
+  void VisitBasicBlock(HBasicBlock* block) OVERRIDE {
+    // TODO: fragile iteration, provide more robust iterators?
+    for (HInstructionIterator it(block->GetInstructions()); !it.Done(); it.Advance()) {
+      HInstruction* instruction = it.Current();
+      if (instruction->IsInBlock()) {
+        instruction->Accept(this);
+      }
+    }
+  }
+
   void VisitAnd(HAnd* instruction) OVERRIDE;
   void VisitArrayGet(HArrayGet* instruction) OVERRIDE;
   void VisitArraySet(HArraySet* instruction) OVERRIDE;
+  void VisitMul(HMul* instruction) OVERRIDE;
+  void VisitOr(HOr* instruction) OVERRIDE;
+  void VisitShl(HShl* instruction) OVERRIDE;
+  void VisitShr(HShr* instruction) OVERRIDE;
+  void VisitTypeConversion(HTypeConversion* instruction) OVERRIDE;
+  void VisitUShr(HUShr* instruction) OVERRIDE;
 
   OptimizingCompilerStats* stats_;
 };
diff --git a/compiler/optimizing/instruction_simplifier_arm64.cc b/compiler/optimizing/instruction_simplifier_arm64.cc
index 6d107d5..73b7b2b 100644
--- a/compiler/optimizing/instruction_simplifier_arm64.cc
+++ b/compiler/optimizing/instruction_simplifier_arm64.cc
@@ -22,16 +22,18 @@
 #include "mirror/string.h"
 
 namespace art {
-namespace arm64 {
 
 using helpers::CanFitInShifterOperand;
 using helpers::HasShifterOperand;
+
+namespace arm64 {
+
 using helpers::ShifterOperandSupportsExtension;
 
 bool InstructionSimplifierArm64Visitor::TryMergeIntoShifterOperand(HInstruction* use,
                                                                    HInstruction* bitfield_op,
                                                                    bool do_merge) {
-  DCHECK(HasShifterOperand(use));
+  DCHECK(HasShifterOperand(use, kArm64));
   DCHECK(use->IsBinaryOperation() || use->IsNeg());
   DCHECK(CanFitInShifterOperand(bitfield_op));
   DCHECK(!bitfield_op->HasEnvironmentUses());
@@ -72,23 +74,22 @@
     }
   }
 
-  HArm64DataProcWithShifterOp::OpKind op_kind;
+  HDataProcWithShifterOp::OpKind op_kind;
   int shift_amount = 0;
-  HArm64DataProcWithShifterOp::GetOpInfoFromInstruction(bitfield_op, &op_kind, &shift_amount);
+  HDataProcWithShifterOp::GetOpInfoFromInstruction(bitfield_op, &op_kind, &shift_amount);
 
-  if (HArm64DataProcWithShifterOp::IsExtensionOp(op_kind) &&
-      !ShifterOperandSupportsExtension(use)) {
+  if (HDataProcWithShifterOp::IsExtensionOp(op_kind) && !ShifterOperandSupportsExtension(use)) {
     return false;
   }
 
   if (do_merge) {
-    HArm64DataProcWithShifterOp* alu_with_op =
-        new (GetGraph()->GetArena()) HArm64DataProcWithShifterOp(use,
-                                                                 other_input,
-                                                                 bitfield_op->InputAt(0),
-                                                                 op_kind,
-                                                                 shift_amount,
-                                                                 use->GetDexPc());
+    HDataProcWithShifterOp* alu_with_op =
+        new (GetGraph()->GetArena()) HDataProcWithShifterOp(use,
+                                                            other_input,
+                                                            bitfield_op->InputAt(0),
+                                                            op_kind,
+                                                            shift_amount,
+                                                            use->GetDexPc());
     use->GetBlock()->ReplaceAndRemoveInstructionWith(use, alu_with_op);
     if (bitfield_op->GetUses().empty()) {
       bitfield_op->GetBlock()->RemoveInstruction(bitfield_op);
@@ -112,7 +113,7 @@
   // Check whether we can merge the instruction in all its users' shifter operand.
   for (const HUseListNode<HInstruction*>& use : uses) {
     HInstruction* user = use.GetUser();
-    if (!HasShifterOperand(user)) {
+    if (!HasShifterOperand(user, kArm64)) {
       return false;
     }
     if (!CanMergeIntoShifterOperand(user, bitfield_op)) {
diff --git a/compiler/optimizing/instruction_simplifier_arm64.h b/compiler/optimizing/instruction_simplifier_arm64.h
index d4cb1f1..65654f5 100644
--- a/compiler/optimizing/instruction_simplifier_arm64.h
+++ b/compiler/optimizing/instruction_simplifier_arm64.h
@@ -40,11 +40,11 @@
                                   HInstruction* bitfield_op,
                                   bool do_merge);
   bool CanMergeIntoShifterOperand(HInstruction* use, HInstruction* bitfield_op) {
-    return TryMergeIntoShifterOperand(use, bitfield_op, false);
+    return TryMergeIntoShifterOperand(use, bitfield_op, /* do_merge */ false);
   }
   bool MergeIntoShifterOperand(HInstruction* use, HInstruction* bitfield_op) {
     DCHECK(CanMergeIntoShifterOperand(use, bitfield_op));
-    return TryMergeIntoShifterOperand(use, bitfield_op, true);
+    return TryMergeIntoShifterOperand(use, bitfield_op, /* do_merge */ true);
   }
 
   /**
diff --git a/compiler/optimizing/instruction_simplifier_shared.h b/compiler/optimizing/instruction_simplifier_shared.h
index 56804f5..83e3ffc 100644
--- a/compiler/optimizing/instruction_simplifier_shared.h
+++ b/compiler/optimizing/instruction_simplifier_shared.h
@@ -21,6 +21,33 @@
 
 namespace art {
 
+namespace helpers {
+
+inline bool CanFitInShifterOperand(HInstruction* instruction) {
+  if (instruction->IsTypeConversion()) {
+    HTypeConversion* conversion = instruction->AsTypeConversion();
+    Primitive::Type result_type = conversion->GetResultType();
+    Primitive::Type input_type = conversion->GetInputType();
+    // We don't expect to see the same type as input and result.
+    return Primitive::IsIntegralType(result_type) && Primitive::IsIntegralType(input_type) &&
+        (result_type != input_type);
+  } else {
+    return (instruction->IsShl() && instruction->AsShl()->InputAt(1)->IsIntConstant()) ||
+        (instruction->IsShr() && instruction->AsShr()->InputAt(1)->IsIntConstant()) ||
+        (instruction->IsUShr() && instruction->AsUShr()->InputAt(1)->IsIntConstant());
+  }
+}
+
+inline bool HasShifterOperand(HInstruction* instr, InstructionSet isa) {
+  // On ARM64 `neg` instructions are an alias of `sub` using the zero register
+  // as the first register input.
+  bool res = instr->IsAdd() || instr->IsAnd() || (isa == kArm64 && instr->IsNeg()) ||
+      instr->IsOr() || instr->IsSub() || instr->IsXor();
+  return res;
+}
+
+}  // namespace helpers
+
 bool TryCombineMultiplyAccumulate(HMul* mul, InstructionSet isa);
 // For bitwise operations (And/Or/Xor) with a negated input, try to use
 // a negated bitwise instruction.
diff --git a/compiler/optimizing/nodes.h b/compiler/optimizing/nodes.h
index 2f258db..8a9e618 100644
--- a/compiler/optimizing/nodes.h
+++ b/compiler/optimizing/nodes.h
@@ -1362,6 +1362,7 @@
 #else
 #define FOR_EACH_CONCRETE_INSTRUCTION_SHARED(M)                         \
   M(BitwiseNegatedRight, Instruction)                                   \
+  M(DataProcWithShifterOp, Instruction)                                 \
   M(MultiplyAccumulate, Instruction)                                    \
   M(IntermediateAddress, Instruction)
 #endif
@@ -1373,12 +1374,7 @@
   M(ArmDexCacheArraysBase, Instruction)
 #endif
 
-#ifndef ART_ENABLE_CODEGEN_arm64
 #define FOR_EACH_CONCRETE_INSTRUCTION_ARM64(M)
-#else
-#define FOR_EACH_CONCRETE_INSTRUCTION_ARM64(M)                          \
-  M(Arm64DataProcWithShifterOp, Instruction)
-#endif
 
 #ifndef ART_ENABLE_CODEGEN_mips
 #define FOR_EACH_CONCRETE_INSTRUCTION_MIPS(M)
@@ -6619,9 +6615,6 @@
 #ifdef ART_ENABLE_CODEGEN_arm
 #include "nodes_arm.h"
 #endif
-#ifdef ART_ENABLE_CODEGEN_arm64
-#include "nodes_arm64.h"
-#endif
 #ifdef ART_ENABLE_CODEGEN_mips
 #include "nodes_mips.h"
 #endif
diff --git a/compiler/optimizing/nodes_arm64.h b/compiler/optimizing/nodes_arm64.h
deleted file mode 100644
index 3f88717..0000000
--- a/compiler/optimizing/nodes_arm64.h
+++ /dev/null
@@ -1,99 +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_OPTIMIZING_NODES_ARM64_H_
-#define ART_COMPILER_OPTIMIZING_NODES_ARM64_H_
-
-#include "nodes.h"
-
-namespace art {
-
-class HArm64DataProcWithShifterOp FINAL : public HExpression<2> {
- public:
-  enum OpKind {
-    kLSL,   // Logical shift left.
-    kLSR,   // Logical shift right.
-    kASR,   // Arithmetic shift right.
-    kUXTB,  // Unsigned extend byte.
-    kUXTH,  // Unsigned extend half-word.
-    kUXTW,  // Unsigned extend word.
-    kSXTB,  // Signed extend byte.
-    kSXTH,  // Signed extend half-word.
-    kSXTW,  // Signed extend word.
-
-    // Aliases.
-    kFirstShiftOp = kLSL,
-    kLastShiftOp = kASR,
-    kFirstExtensionOp = kUXTB,
-    kLastExtensionOp = kSXTW
-  };
-  HArm64DataProcWithShifterOp(HInstruction* instr,
-                              HInstruction* left,
-                              HInstruction* right,
-                              OpKind op,
-                              // The shift argument is unused if the operation
-                              // is an extension.
-                              int shift = 0,
-                              uint32_t dex_pc = kNoDexPc)
-      : HExpression(instr->GetType(), SideEffects::None(), dex_pc),
-        instr_kind_(instr->GetKind()), op_kind_(op), shift_amount_(shift) {
-    DCHECK(!instr->HasSideEffects());
-    SetRawInputAt(0, left);
-    SetRawInputAt(1, right);
-  }
-
-  bool CanBeMoved() const OVERRIDE { return true; }
-  bool InstructionDataEquals(const HInstruction* other_instr) const OVERRIDE {
-    const HArm64DataProcWithShifterOp* other = other_instr->AsArm64DataProcWithShifterOp();
-    return instr_kind_ == other->instr_kind_ &&
-        op_kind_ == other->op_kind_ &&
-        shift_amount_ == other->shift_amount_;
-  }
-
-  static bool IsShiftOp(OpKind op_kind) {
-    return kFirstShiftOp <= op_kind && op_kind <= kLastShiftOp;
-  }
-
-  static bool IsExtensionOp(OpKind op_kind) {
-    return kFirstExtensionOp <= op_kind && op_kind <= kLastExtensionOp;
-  }
-
-  // Find the operation kind and shift amount from a bitfield move instruction.
-  static void GetOpInfoFromInstruction(HInstruction* bitfield_op,
-                                       /*out*/OpKind* op_kind,
-                                       /*out*/int* shift_amount);
-
-  InstructionKind GetInstrKind() const { return instr_kind_; }
-  OpKind GetOpKind() const { return op_kind_; }
-  int GetShiftAmount() const { return shift_amount_; }
-
-  DECLARE_INSTRUCTION(Arm64DataProcWithShifterOp);
-
- private:
-  InstructionKind instr_kind_;
-  OpKind op_kind_;
-  int shift_amount_;
-
-  friend std::ostream& operator<<(std::ostream& os, OpKind op);
-
-  DISALLOW_COPY_AND_ASSIGN(HArm64DataProcWithShifterOp);
-};
-
-std::ostream& operator<<(std::ostream& os, const HArm64DataProcWithShifterOp::OpKind op);
-
-}  // namespace art
-
-#endif  // ART_COMPILER_OPTIMIZING_NODES_ARM64_H_
diff --git a/compiler/optimizing/nodes_arm64.cc b/compiler/optimizing/nodes_shared.cc
similarity index 63%
rename from compiler/optimizing/nodes_arm64.cc
rename to compiler/optimizing/nodes_shared.cc
index ac2f093..f145bf9 100644
--- a/compiler/optimizing/nodes_arm64.cc
+++ b/compiler/optimizing/nodes_shared.cc
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2015 The Android Open Source Project
+ * 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.
@@ -15,15 +15,15 @@
  */
 
 #include "common_arm64.h"
-#include "nodes.h"
+#include "nodes_shared.h"
 
 namespace art {
 
-using arm64::helpers::CanFitInShifterOperand;
+using helpers::CanFitInShifterOperand;
 
-void HArm64DataProcWithShifterOp::GetOpInfoFromInstruction(HInstruction* instruction,
-                                                           /*out*/OpKind* op_kind,
-                                                           /*out*/int* shift_amount) {
+void HDataProcWithShifterOp::GetOpInfoFromInstruction(HInstruction* instruction,
+                                                      /*out*/OpKind* op_kind,
+                                                      /*out*/int* shift_amount) {
   DCHECK(CanFitInShifterOperand(instruction));
   if (instruction->IsShl()) {
     *op_kind = kLSL;
@@ -41,12 +41,11 @@
     int result_size = Primitive::ComponentSize(result_type);
     int input_size = Primitive::ComponentSize(input_type);
     int min_size = std::min(result_size, input_size);
-    // This follows the logic in
-    // `InstructionCodeGeneratorARM64::VisitTypeConversion()`.
     if (result_type == Primitive::kPrimInt && input_type == Primitive::kPrimLong) {
-      // There is actually nothing to do. The register will be used as a W
-      // register, discarding the top bits. This is represented by the default
-      // encoding 'LSL 0'.
+      // There is actually nothing to do. On ARM the high register from the
+      // pair will be ignored. On ARM64 the register will be used as a W
+      // register, discarding the top bits. This is represented by the
+      // default encoding 'LSL 0'.
       *op_kind = kLSL;
       *shift_amount = 0;
     } else if (result_type == Primitive::kPrimChar ||
@@ -64,17 +63,17 @@
   }
 }
 
-std::ostream& operator<<(std::ostream& os, const HArm64DataProcWithShifterOp::OpKind op) {
+std::ostream& operator<<(std::ostream& os, const HDataProcWithShifterOp::OpKind op) {
   switch (op) {
-    case HArm64DataProcWithShifterOp::kLSL:  return os << "LSL";
-    case HArm64DataProcWithShifterOp::kLSR:  return os << "LSR";
-    case HArm64DataProcWithShifterOp::kASR:  return os << "ASR";
-    case HArm64DataProcWithShifterOp::kUXTB: return os << "UXTB";
-    case HArm64DataProcWithShifterOp::kUXTH: return os << "UXTH";
-    case HArm64DataProcWithShifterOp::kUXTW: return os << "UXTW";
-    case HArm64DataProcWithShifterOp::kSXTB: return os << "SXTB";
-    case HArm64DataProcWithShifterOp::kSXTH: return os << "SXTH";
-    case HArm64DataProcWithShifterOp::kSXTW: return os << "SXTW";
+    case HDataProcWithShifterOp::kLSL:  return os << "LSL";
+    case HDataProcWithShifterOp::kLSR:  return os << "LSR";
+    case HDataProcWithShifterOp::kASR:  return os << "ASR";
+    case HDataProcWithShifterOp::kUXTB: return os << "UXTB";
+    case HDataProcWithShifterOp::kUXTH: return os << "UXTH";
+    case HDataProcWithShifterOp::kUXTW: return os << "UXTW";
+    case HDataProcWithShifterOp::kSXTB: return os << "SXTB";
+    case HDataProcWithShifterOp::kSXTH: return os << "SXTH";
+    case HDataProcWithShifterOp::kSXTW: return os << "SXTW";
     default:
       LOG(FATAL) << "Invalid OpKind " << static_cast<int>(op);
       UNREACHABLE();
diff --git a/compiler/optimizing/nodes_shared.h b/compiler/optimizing/nodes_shared.h
index 814202e..c6bfbcc 100644
--- a/compiler/optimizing/nodes_shared.h
+++ b/compiler/optimizing/nodes_shared.h
@@ -150,6 +150,81 @@
   DISALLOW_COPY_AND_ASSIGN(HIntermediateAddress);
 };
 
+class HDataProcWithShifterOp FINAL : public HExpression<2> {
+ public:
+  enum OpKind {
+    kLSL,   // Logical shift left.
+    kLSR,   // Logical shift right.
+    kASR,   // Arithmetic shift right.
+    kUXTB,  // Unsigned extend byte.
+    kUXTH,  // Unsigned extend half-word.
+    kUXTW,  // Unsigned extend word.
+    kSXTB,  // Signed extend byte.
+    kSXTH,  // Signed extend half-word.
+    kSXTW,  // Signed extend word.
+
+    // Aliases.
+    kFirstShiftOp = kLSL,
+    kLastShiftOp = kASR,
+    kFirstExtensionOp = kUXTB,
+    kLastExtensionOp = kSXTW
+  };
+  HDataProcWithShifterOp(HInstruction* instr,
+                         HInstruction* left,
+                         HInstruction* right,
+                         OpKind op,
+                         // The shift argument is unused if the operation
+                         // is an extension.
+                         int shift = 0,
+                         uint32_t dex_pc = kNoDexPc)
+      : HExpression(instr->GetType(), SideEffects::None(), dex_pc),
+        instr_kind_(instr->GetKind()), op_kind_(op),
+        shift_amount_(shift & (instr->GetType() == Primitive::kPrimInt
+            ? kMaxIntShiftDistance
+            : kMaxLongShiftDistance)) {
+    DCHECK(!instr->HasSideEffects());
+    SetRawInputAt(0, left);
+    SetRawInputAt(1, right);
+  }
+
+  bool CanBeMoved() const OVERRIDE { return true; }
+  bool InstructionDataEquals(const HInstruction* other_instr) const OVERRIDE {
+    const HDataProcWithShifterOp* other = other_instr->AsDataProcWithShifterOp();
+    return instr_kind_ == other->instr_kind_ &&
+        op_kind_ == other->op_kind_ &&
+        shift_amount_ == other->shift_amount_;
+  }
+
+  static bool IsShiftOp(OpKind op_kind) {
+    return kFirstShiftOp <= op_kind && op_kind <= kLastShiftOp;
+  }
+
+  static bool IsExtensionOp(OpKind op_kind) {
+    return kFirstExtensionOp <= op_kind && op_kind <= kLastExtensionOp;
+  }
+
+  // Find the operation kind and shift amount from a bitfield move instruction.
+  static void GetOpInfoFromInstruction(HInstruction* bitfield_op,
+                                       /*out*/OpKind* op_kind,
+                                       /*out*/int* shift_amount);
+
+  InstructionKind GetInstrKind() const { return instr_kind_; }
+  OpKind GetOpKind() const { return op_kind_; }
+  int GetShiftAmount() const { return shift_amount_; }
+
+  DECLARE_INSTRUCTION(DataProcWithShifterOp);
+
+ private:
+  InstructionKind instr_kind_;
+  OpKind op_kind_;
+  int shift_amount_;
+
+  friend std::ostream& operator<<(std::ostream& os, OpKind op);
+
+  DISALLOW_COPY_AND_ASSIGN(HDataProcWithShifterOp);
+};
+
+std::ostream& operator<<(std::ostream& os, const HDataProcWithShifterOp::OpKind op);
 
 }  // namespace art
 
diff --git a/compiler/optimizing/optimizing_cfi_test.cc b/compiler/optimizing/optimizing_cfi_test.cc
index 0e02311..490e50c 100644
--- a/compiler/optimizing/optimizing_cfi_test.cc
+++ b/compiler/optimizing/optimizing_cfi_test.cc
@@ -24,17 +24,17 @@
 #include "optimizing/code_generator.h"
 #include "optimizing/optimizing_unit_test.h"
 #include "utils/assembler.h"
-#ifdef ART_USE_VIXL_ARM_BACKEND
-#include "utils/arm/assembler_arm_vixl.h"
-#else
+#ifdef ART_USE_OLD_ARM_BACKEND
 #include "utils/arm/assembler_thumb2.h"
+#else
+#include "utils/arm/assembler_arm_vixl.h"
 #endif
 #include "utils/mips/assembler_mips.h"
 #include "utils/mips64/assembler_mips64.h"
 
 #include "optimizing/optimizing_cfi_test_expected.inc"
 
-#ifdef ART_USE_VIXL_ARM_BACKEND
+#ifndef ART_USE_OLD_ARM_BACKEND
 namespace vixl32 = vixl::aarch32;
 
 using vixl32::r0;
@@ -196,7 +196,15 @@
       expected_cfi_kThumb2_adjust,
       expected_cfi_kThumb2_adjust + arraysize(expected_cfi_kThumb2_adjust));
   SetUpFrame(kThumb2);
-#ifdef ART_USE_VIXL_ARM_BACKEND
+#ifdef ART_USE_OLD_ARM_BACKEND
+#define __ down_cast<arm::Thumb2Assembler*>(GetCodeGenerator()->GetAssembler())->
+  Label target;
+  __ CompareAndBranchIfZero(arm::R0, &target);
+  // Push the target out of range of CBZ.
+  for (size_t i = 0; i != 65; ++i) {
+    __ ldr(arm::R0, arm::Address(arm::R0));
+  }
+#else
 #define __ down_cast<arm::ArmVIXLAssembler*>(GetCodeGenerator() \
     ->GetAssembler())->GetVIXLAssembler()->
   vixl32::Label target;
@@ -205,14 +213,6 @@
   for (size_t i = 0; i != 65; ++i) {
     __ Ldr(r0, vixl32::MemOperand(r0));
   }
-#else
-#define __ down_cast<arm::Thumb2Assembler*>(GetCodeGenerator()->GetAssembler())->
-  Label target;
-  __ CompareAndBranchIfZero(arm::R0, &target);
-  // Push the target out of range of CBZ.
-  for (size_t i = 0; i != 65; ++i) {
-    __ ldr(arm::R0, arm::Address(arm::R0));
-  }
 #endif
   __ Bind(&target);
 #undef __
diff --git a/compiler/optimizing/optimizing_cfi_test_expected.inc b/compiler/optimizing/optimizing_cfi_test_expected.inc
index 82670c3..d84fe6c 100644
--- a/compiler/optimizing/optimizing_cfi_test_expected.inc
+++ b/compiler/optimizing/optimizing_cfi_test_expected.inc
@@ -223,15 +223,15 @@
 // 0x00000040: .cfi_def_cfa_offset: 64
 
 static constexpr uint8_t expected_asm_kThumb2_adjust[] = {
-#ifdef ART_USE_VIXL_ARM_BACKEND
-    // VIXL emits an extra 2 bytes here for a 32-bit beq as there is no
-    // optimistic 16-bit emit and subsequent fixup for out of reach targets
-    // as with the current assembler.
-    0x60, 0xB5, 0x2D, 0xED, 0x02, 0x8A, 0x8B, 0xB0, 0x00, 0x28, 0x00, 0xF0,
-    0x41, 0x80, 0x00, 0x68, 0x00, 0x68, 0x00, 0x68, 0x00, 0x68, 0x00, 0x68,
-#else
+#ifdef ART_USE_OLD_ARM_BACKEND
     0x60, 0xB5, 0x2D, 0xED, 0x02, 0x8A, 0x8B, 0xB0, 0x00, 0x28,
     0x40, 0xD0, 0x00, 0x68, 0x00, 0x68, 0x00, 0x68, 0x00, 0x68, 0x00, 0x68,
+#else
+    // VIXL emits an extra 2 bytes here for a 32-bit beq as there is no
+    // optimistic 16-bit emit and subsequent fixup for out of reach targets
+    // as with the old assembler.
+    0x60, 0xB5, 0x2D, 0xED, 0x02, 0x8A, 0x8B, 0xB0, 0x00, 0x28, 0x00, 0xF0,
+    0x41, 0x80, 0x00, 0x68, 0x00, 0x68, 0x00, 0x68, 0x00, 0x68, 0x00, 0x68,
 #endif
     0x00, 0x68, 0x00, 0x68, 0x00, 0x68, 0x00, 0x68, 0x00, 0x68, 0x00, 0x68,
     0x00, 0x68, 0x00, 0x68, 0x00, 0x68, 0x00, 0x68, 0x00, 0x68, 0x00, 0x68,
@@ -247,10 +247,10 @@
 };
 static constexpr uint8_t expected_cfi_kThumb2_adjust[] = {
     0x42, 0x0E, 0x0C, 0x85, 0x03, 0x86, 0x02, 0x8E, 0x01, 0x44, 0x0E, 0x14,
-#ifdef ART_USE_VIXL_ARM_BACKEND
-    0x05, 0x50, 0x05, 0x05, 0x51, 0x04, 0x42, 0x0E, 0x40, 0x02, 0x88, 0x0A,
-#else
+#ifdef ART_USE_OLD_ARM_BACKEND
     0x05, 0x50, 0x05, 0x05, 0x51, 0x04, 0x42, 0x0E, 0x40, 0x02, 0x86, 0x0A,
+#else
+    0x05, 0x50, 0x05, 0x05, 0x51, 0x04, 0x42, 0x0E, 0x40, 0x02, 0x88, 0x0A,
 #endif
     0x42, 0x0E, 0x14, 0x44, 0x0E, 0x0C, 0x06, 0x50, 0x06, 0x51, 0x42, 0x0B,
     0x0E, 0x40,
diff --git a/compiler/optimizing/optimizing_compiler.cc b/compiler/optimizing/optimizing_compiler.cc
index 8638e34..f72bd6a 100644
--- a/compiler/optimizing/optimizing_compiler.cc
+++ b/compiler/optimizing/optimizing_compiler.cc
@@ -306,7 +306,7 @@
                           InvokeType invoke_type,
                           uint16_t class_def_idx,
                           uint32_t method_idx,
-                          jobject class_loader,
+                          Handle<mirror::ClassLoader> class_loader,
                           const DexFile& dex_file,
                           Handle<mirror::DexCache> dex_cache) const OVERRIDE;
 
@@ -375,7 +375,7 @@
                             InvokeType invoke_type,
                             uint16_t class_def_idx,
                             uint32_t method_idx,
-                            jobject class_loader,
+                            Handle<mirror::ClassLoader> class_loader,
                             const DexFile& dex_file,
                             Handle<mirror::DexCache> dex_cache,
                             ArtMethod* method,
@@ -875,7 +875,7 @@
                                               InvokeType invoke_type,
                                               uint16_t class_def_idx,
                                               uint32_t method_idx,
-                                              jobject class_loader,
+                                              Handle<mirror::ClassLoader> class_loader,
                                               const DexFile& dex_file,
                                               Handle<mirror::DexCache> dex_cache,
                                               ArtMethod* method,
@@ -946,11 +946,8 @@
   const uint8_t* interpreter_metadata = nullptr;
   if (method == nullptr) {
     ScopedObjectAccess soa(Thread::Current());
-    StackHandleScope<1> hs(soa.Self());
-    Handle<mirror::ClassLoader> loader(hs.NewHandle(
-        soa.Decode<mirror::ClassLoader>(class_loader)));
     method = compiler_driver->ResolveMethod(
-        soa, dex_cache, loader, &dex_compilation_unit, method_idx, invoke_type);
+        soa, dex_cache, class_loader, &dex_compilation_unit, method_idx, invoke_type);
   }
   // For AOT compilation, we may not get a method, for example if its class is erroneous.
   // JIT should always have a method.
@@ -959,16 +956,6 @@
     graph->SetArtMethod(method);
     ScopedObjectAccess soa(Thread::Current());
     interpreter_metadata = method->GetQuickenedInfo(class_linker->GetImagePointerSize());
-    dex::TypeIndex type_index = method->GetDeclaringClass()->GetDexTypeIndex();
-
-    // Update the dex cache if the type is not in it yet. Note that under AOT,
-    // the verifier must have set it, but under JIT, there's no guarantee, as we
-    // don't necessarily run the verifier.
-    // The compiler and the compiler driver assume the compiling class is
-    // in the dex cache.
-    if (dex_cache->GetResolvedType(type_index) == nullptr) {
-      dex_cache->SetResolvedType(type_index, method->GetDeclaringClass());
-    }
   }
 
   std::unique_ptr<CodeGenerator> codegen(
@@ -1049,7 +1036,7 @@
                                             InvokeType invoke_type,
                                             uint16_t class_def_idx,
                                             uint32_t method_idx,
-                                            jobject jclass_loader,
+                                            Handle<mirror::ClassLoader> jclass_loader,
                                             const DexFile& dex_file,
                                             Handle<mirror::DexCache> dex_cache) const {
   CompilerDriver* compiler_driver = GetCompilerDriver();
@@ -1163,7 +1150,6 @@
   Handle<mirror::DexCache> dex_cache(hs.NewHandle(method->GetDexCache()));
   DCHECK(method->IsCompilable());
 
-  jobject jclass_loader = class_loader.ToJObject();
   const DexFile* dex_file = method->GetDexFile();
   const uint16_t class_def_idx = method->GetClassDefIndex();
   const DexFile::CodeItem* code_item = dex_file->GetCodeItem(method->GetCodeItemOffset());
@@ -1187,7 +1173,7 @@
                    invoke_type,
                    class_def_idx,
                    method_idx,
-                   jclass_loader,
+                   class_loader,
                    *dex_file,
                    dex_cache,
                    method,
diff --git a/compiler/optimizing/reference_type_propagation.cc b/compiler/optimizing/reference_type_propagation.cc
index c55fccc..6e332ca 100644
--- a/compiler/optimizing/reference_type_propagation.cc
+++ b/compiler/optimizing/reference_type_propagation.cc
@@ -65,11 +65,13 @@
 class ReferenceTypePropagation::RTPVisitor : public HGraphDelegateVisitor {
  public:
   RTPVisitor(HGraph* graph,
+             Handle<mirror::ClassLoader> class_loader,
              Handle<mirror::DexCache> hint_dex_cache,
              HandleCache* handle_cache,
              ArenaVector<HInstruction*>* worklist,
              bool is_first_run)
     : HGraphDelegateVisitor(graph),
+      class_loader_(class_loader),
       hint_dex_cache_(hint_dex_cache),
       handle_cache_(handle_cache),
       worklist_(worklist),
@@ -101,6 +103,7 @@
                                bool is_exact);
 
  private:
+  Handle<mirror::ClassLoader> class_loader_;
   Handle<mirror::DexCache> hint_dex_cache_;
   HandleCache* handle_cache_;
   ArenaVector<HInstruction*>* worklist_;
@@ -108,11 +111,13 @@
 };
 
 ReferenceTypePropagation::ReferenceTypePropagation(HGraph* graph,
+                                                   Handle<mirror::ClassLoader> class_loader,
                                                    Handle<mirror::DexCache> hint_dex_cache,
                                                    VariableSizedHandleScope* handles,
                                                    bool is_first_run,
                                                    const char* name)
     : HOptimization(graph, name),
+      class_loader_(class_loader),
       hint_dex_cache_(hint_dex_cache),
       handle_cache_(handles),
       worklist_(graph->GetArena()->Adapter(kArenaAllocReferenceTypePropagation)),
@@ -147,7 +152,12 @@
 }
 
 void ReferenceTypePropagation::Visit(HInstruction* instruction) {
-  RTPVisitor visitor(graph_, hint_dex_cache_, &handle_cache_, &worklist_, is_first_run_);
+  RTPVisitor visitor(graph_,
+                     class_loader_,
+                     hint_dex_cache_,
+                     &handle_cache_,
+                     &worklist_,
+                     is_first_run_);
   instruction->Accept(&visitor);
 }
 
@@ -321,7 +331,12 @@
 }
 
 void ReferenceTypePropagation::VisitBasicBlock(HBasicBlock* block) {
-  RTPVisitor visitor(graph_, hint_dex_cache_, &handle_cache_, &worklist_, is_first_run_);
+  RTPVisitor visitor(graph_,
+                     class_loader_,
+                     hint_dex_cache_,
+                     &handle_cache_,
+                     &worklist_,
+                     is_first_run_);
   // Handle Phis first as there might be instructions in the same block who depend on them.
   for (HInstructionIterator it(block->GetPhis()); !it.Done(); it.Advance()) {
     VisitPhi(it.Current()->AsPhi());
@@ -542,8 +557,9 @@
 
   ScopedObjectAccess soa(Thread::Current());
   ObjPtr<mirror::DexCache> dex_cache = FindDexCacheWithHint(soa.Self(), dex_file, hint_dex_cache_);
-  // Get type from dex cache assuming it was populated by the verifier.
-  SetClassAsTypeInfo(instr, dex_cache->GetResolvedType(type_idx), is_exact);
+  ObjPtr<mirror::Class> klass =
+      ClassLinker::LookupResolvedType(type_idx, dex_cache, class_loader_.Get());
+  SetClassAsTypeInfo(instr, klass, is_exact);
 }
 
 void ReferenceTypePropagation::RTPVisitor::VisitNewInstance(HNewInstance* instr) {
@@ -556,25 +572,13 @@
   SetClassAsTypeInfo(instr, instr->GetLoadClass()->GetClass().Get(), /* is_exact */ true);
 }
 
-static mirror::Class* GetClassFromDexCache(Thread* self,
-                                           const DexFile& dex_file,
-                                           dex::TypeIndex type_idx,
-                                           Handle<mirror::DexCache> hint_dex_cache)
-    REQUIRES_SHARED(Locks::mutator_lock_) {
-  ObjPtr<mirror::DexCache> dex_cache = FindDexCacheWithHint(self, dex_file, hint_dex_cache);
-  // Get type from dex cache assuming it was populated by the verifier.
-  return dex_cache->GetResolvedType(type_idx);
-}
-
 void ReferenceTypePropagation::RTPVisitor::VisitParameterValue(HParameterValue* instr) {
   // We check if the existing type is valid: the inliner may have set it.
   if (instr->GetType() == Primitive::kPrimNot && !instr->GetReferenceTypeInfo().IsValid()) {
-    ScopedObjectAccess soa(Thread::Current());
-    mirror::Class* resolved_class = GetClassFromDexCache(soa.Self(),
-                                                         instr->GetDexFile(),
-                                                         instr->GetTypeIndex(),
-                                                         hint_dex_cache_);
-    SetClassAsTypeInfo(instr, resolved_class, /* is_exact */ false);
+    UpdateReferenceTypeInfo(instr,
+                            instr->GetTypeIndex(),
+                            instr->GetDexFile(),
+                            /* is_exact */ false);
   }
 }
 
diff --git a/compiler/optimizing/reference_type_propagation.h b/compiler/optimizing/reference_type_propagation.h
index 4663471..215e967 100644
--- a/compiler/optimizing/reference_type_propagation.h
+++ b/compiler/optimizing/reference_type_propagation.h
@@ -33,6 +33,7 @@
 class ReferenceTypePropagation : public HOptimization {
  public:
   ReferenceTypePropagation(HGraph* graph,
+                           Handle<mirror::ClassLoader> class_loader,
                            Handle<mirror::DexCache> hint_dex_cache,
                            VariableSizedHandleScope* handles,
                            bool is_first_run,
@@ -105,6 +106,8 @@
 
   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 b061c87..84a4bab 100644
--- a/compiler/optimizing/reference_type_propagation_test.cc
+++ b/compiler/optimizing/reference_type_propagation_test.cc
@@ -38,6 +38,7 @@
   void SetupPropagation(VariableSizedHandleScope* handles) {
     graph_->InitializeInexactObjectRTI(handles);
     propagation_ = new (&allocator_) ReferenceTypePropagation(graph_,
+                                                              Handle<mirror::ClassLoader>(),
                                                               Handle<mirror::DexCache>(),
                                                               handles,
                                                               true,
diff --git a/compiler/optimizing/scheduler_arm64.cc b/compiler/optimizing/scheduler_arm64.cc
index e3701fb..558dcc4 100644
--- a/compiler/optimizing/scheduler_arm64.cc
+++ b/compiler/optimizing/scheduler_arm64.cc
@@ -31,8 +31,8 @@
   last_visited_latency_ = kArm64IntegerOpLatency;
 }
 
-void SchedulingLatencyVisitorARM64::VisitArm64DataProcWithShifterOp(
-    HArm64DataProcWithShifterOp* ATTRIBUTE_UNUSED) {
+void SchedulingLatencyVisitorARM64::VisitDataProcWithShifterOp(
+    HDataProcWithShifterOp* ATTRIBUTE_UNUSED) {
   last_visited_latency_ = kArm64DataProcWithShifterOpLatency;
 }
 
diff --git a/compiler/optimizing/scheduler_arm64.h b/compiler/optimizing/scheduler_arm64.h
index 702027c..7a33720 100644
--- a/compiler/optimizing/scheduler_arm64.h
+++ b/compiler/optimizing/scheduler_arm64.h
@@ -74,7 +74,8 @@
 #define FOR_EACH_SCHEDULED_SHARED_INSTRUCTION(M) \
   M(BitwiseNegatedRight, unused)                 \
   M(MultiplyAccumulate, unused)                  \
-  M(IntermediateAddress, unused)
+  M(IntermediateAddress, unused)                 \
+  M(DataProcWithShifterOp, unused)
 
 #define DECLARE_VISIT_INSTRUCTION(type, unused)  \
   void Visit##type(H##type* instruction) OVERRIDE;
diff --git a/compiler/optimizing/ssa_builder.cc b/compiler/optimizing/ssa_builder.cc
index 487e4dd..50ab11b 100644
--- a/compiler/optimizing/ssa_builder.cc
+++ b/compiler/optimizing/ssa_builder.cc
@@ -499,7 +499,11 @@
 
   // 4) Compute type of reference type instructions. The pass assumes that
   // NullConstant has been fixed up.
-  ReferenceTypePropagation(graph_, dex_cache_, handles_, /* is_first_run */ true).Run();
+  ReferenceTypePropagation(graph_,
+                           class_loader_,
+                           dex_cache_,
+                           handles_,
+                           /* is_first_run */ true).Run();
 
   // 5) HInstructionBuilder duplicated ArrayGet instructions with ambiguous type
   // (int/float or long/double) and marked ArraySets with ambiguous input type.
diff --git a/compiler/optimizing/ssa_builder.h b/compiler/optimizing/ssa_builder.h
index 45dac54..978f113 100644
--- a/compiler/optimizing/ssa_builder.h
+++ b/compiler/optimizing/ssa_builder.h
@@ -48,9 +48,11 @@
 class SsaBuilder : public ValueObject {
  public:
   SsaBuilder(HGraph* graph,
+             Handle<mirror::ClassLoader> class_loader,
              Handle<mirror::DexCache> dex_cache,
              VariableSizedHandleScope* handles)
       : graph_(graph),
+        class_loader_(class_loader),
         dex_cache_(dex_cache),
         handles_(handles),
         agets_fixed_(false),
@@ -115,6 +117,7 @@
   void RemoveRedundantUninitializedStrings();
 
   HGraph* graph_;
+  Handle<mirror::ClassLoader> class_loader_;
   Handle<mirror::DexCache> dex_cache_;
   VariableSizedHandleScope* const handles_;
 
diff --git a/compiler/optimizing/stack_map_stream.cc b/compiler/optimizing/stack_map_stream.cc
index eeae96e..4d12ad6 100644
--- a/compiler/optimizing/stack_map_stream.cc
+++ b/compiler/optimizing/stack_map_stream.cc
@@ -16,8 +16,6 @@
 
 #include "stack_map_stream.h"
 
-#include <unordered_map>
-
 #include "art_method-inl.h"
 #include "base/stl_util.h"
 #include "optimizing/optimizing_compiler.h"
@@ -526,7 +524,7 @@
 
 size_t StackMapStream::PrepareRegisterMasks() {
   register_masks_.resize(stack_maps_.size(), 0u);
-  std::unordered_map<uint32_t, size_t> dedupe;
+  ArenaUnorderedMap<uint32_t, size_t> dedupe(allocator_->Adapter(kArenaAllocStackMapStream));
   for (StackMapEntry& stack_map : stack_maps_) {
     const size_t index = dedupe.size();
     stack_map.register_mask_index = dedupe.emplace(stack_map.register_mask, index).first->second;
@@ -541,10 +539,11 @@
   stack_masks_.resize(byte_entry_size * stack_maps_.size(), 0u);
   // For deduplicating we store the stack masks as byte packed for simplicity. We can bit pack later
   // when copying out from stack_masks_.
-  std::unordered_map<MemoryRegion,
-                     size_t,
-                     FNVHash<MemoryRegion>,
-                     MemoryRegion::ContentEquals> dedup(stack_maps_.size());
+  ArenaUnorderedMap<MemoryRegion,
+                    size_t,
+                    FNVHash<MemoryRegion>,
+                    MemoryRegion::ContentEquals> dedup(
+                        stack_maps_.size(), allocator_->Adapter(kArenaAllocStackMapStream));
   for (StackMapEntry& stack_map : stack_maps_) {
     size_t index = dedup.size();
     MemoryRegion stack_mask(stack_masks_.data() + index * byte_entry_size, byte_entry_size);
diff --git a/compiler/utils/swap_space.cc b/compiler/utils/swap_space.cc
index 1a8f567..a1eb08e 100644
--- a/compiler/utils/swap_space.cc
+++ b/compiler/utils/swap_space.cc
@@ -36,17 +36,17 @@
 static void DumpFreeMap(const FreeBySizeSet& free_by_size) {
   size_t last_size = static_cast<size_t>(-1);
   for (const auto& entry : free_by_size) {
-    if (last_size != entry.first) {
-      last_size = entry.first;
+    if (last_size != entry.size) {
+      last_size = entry.size;
       LOG(INFO) << "Size " << last_size;
     }
-    LOG(INFO) << "  0x" << std::hex << entry.second->Start()
-        << " size=" << std::dec << entry.second->size;
+    LOG(INFO) << "  0x" << std::hex << entry.free_by_start_entry->Start()
+        << " size=" << std::dec << entry.free_by_start_entry->size;
   }
 }
 
 void SwapSpace::RemoveChunk(FreeBySizeSet::const_iterator free_by_size_pos) {
-  auto free_by_start_pos = free_by_size_pos->second;
+  auto free_by_start_pos = free_by_size_pos->free_by_start_entry;
   free_by_size_.erase(free_by_size_pos);
   free_by_start_.erase(free_by_start_pos);
 }
@@ -89,7 +89,7 @@
   // Calculate over free_by_size.
   size_t sum1 = 0;
   for (const auto& entry : free_by_size) {
-    sum1 += entry.second->size;
+    sum1 += entry.free_by_start_entry->size;
   }
 
   // Calculate over free_by_start.
@@ -110,27 +110,52 @@
 
   // Check the free list for something that fits.
   // TODO: Smarter implementation. Global biggest chunk, ...
-  SpaceChunk old_chunk;
   auto it = free_by_start_.empty()
       ? free_by_size_.end()
       : free_by_size_.lower_bound(FreeBySizeEntry { size, free_by_start_.begin() });
   if (it != free_by_size_.end()) {
-    old_chunk = *it->second;
-    RemoveChunk(it);
+    auto entry = it->free_by_start_entry;
+    SpaceChunk old_chunk = *entry;
+    if (old_chunk.size == size) {
+      RemoveChunk(it);
+    } else {
+      // Try to avoid deallocating and allocating the std::set<> nodes.
+      // This would be much simpler if we could use replace() from Boost.Bimap.
+
+      // The free_by_start_ map contains disjoint intervals ordered by the `ptr`.
+      // Shrinking the interval does not affect the ordering.
+      it->free_by_start_entry->ptr += size;
+      it->free_by_start_entry->size -= size;
+
+      // The free_by_size_ map is ordered by the `size` and then `free_by_start_entry->ptr`.
+      // Adjusting the `ptr` above does not change that ordering but decreasing `size` can
+      // push the node before the previous node(s).
+      if (it == free_by_size_.begin()) {
+        it->size -= size;
+      } else {
+        auto prev = it;
+        --prev;
+        FreeBySizeEntry new_value(old_chunk.size - size, entry);
+        if (free_by_size_.key_comp()(*prev, new_value)) {
+          it->size -= size;
+        } else {
+          // Changing in place would break the std::set<> ordering, we need to remove and insert.
+          free_by_size_.erase(it);
+          free_by_size_.insert(new_value);
+        }
+      }
+    }
+    return old_chunk.ptr;
   } else {
     // Not a big enough free chunk, need to increase file size.
-    old_chunk = NewFileChunk(size);
+    SpaceChunk new_chunk = NewFileChunk(size);
+    if (new_chunk.size != size) {
+      // Insert the remainder.
+      SpaceChunk remainder = { new_chunk.ptr + size, new_chunk.size - size };
+      InsertChunk(remainder);
+    }
+    return new_chunk.ptr;
   }
-
-  void* ret = old_chunk.ptr;
-
-  if (old_chunk.size != size) {
-    // Insert the remainder.
-    SpaceChunk new_chunk = { old_chunk.ptr + size, old_chunk.size - size };
-    InsertChunk(new_chunk);
-  }
-
-  return ret;
 }
 
 SwapSpace::SpaceChunk SwapSpace::NewFileChunk(size_t min_size) {
diff --git a/compiler/utils/swap_space.h b/compiler/utils/swap_space.h
index 9600907..c286b82 100644
--- a/compiler/utils/swap_space.h
+++ b/compiler/utils/swap_space.h
@@ -45,8 +45,10 @@
  private:
   // Chunk of space.
   struct SpaceChunk {
-    uint8_t* ptr;
-    size_t size;
+    // 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);
@@ -66,13 +68,21 @@
   typedef std::set<SpaceChunk, SortChunkByPtr> FreeByStartSet;
 
   // Map size to an iterator to free_by_start_'s entry.
-  typedef std::pair<size_t, FreeByStartSet::const_iterator> FreeBySizeEntry;
+  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) {
-      if (lhs.first != rhs.first) {
-        return lhs.first < rhs.first;
+      if (lhs.size != rhs.size) {
+        return lhs.size < rhs.size;
       } else {
-        return lhs.second->Start() < rhs.second->Start();
+        return lhs.free_by_start_entry->Start() < rhs.free_by_start_entry->Start();
       }
     }
   };
diff --git a/compiler/utils/x86/assembler_x86.cc b/compiler/utils/x86/assembler_x86.cc
index 5a466e1..6eab302 100644
--- a/compiler/utils/x86/assembler_x86.cc
+++ b/compiler/utils/x86/assembler_x86.cc
@@ -642,39 +642,6 @@
 }
 
 
-void X86Assembler::psrldq(XmmRegister reg, const Immediate& shift_count) {
-  DCHECK(shift_count.is_uint8());
-
-  AssemblerBuffer::EnsureCapacity ensured(&buffer_);
-  EmitUint8(0x66);
-  EmitUint8(0x0F);
-  EmitUint8(0x73);
-  EmitXmmRegisterOperand(3, reg);
-  EmitUint8(shift_count.value());
-}
-
-
-void X86Assembler::psrlq(XmmRegister reg, const Immediate& shift_count) {
-  DCHECK(shift_count.is_uint8());
-
-  AssemblerBuffer::EnsureCapacity ensured(&buffer_);
-  EmitUint8(0x66);
-  EmitUint8(0x0F);
-  EmitUint8(0x73);
-  EmitXmmRegisterOperand(2, reg);
-  EmitUint8(shift_count.value());
-}
-
-
-void X86Assembler::punpckldq(XmmRegister dst, XmmRegister src) {
-  AssemblerBuffer::EnsureCapacity ensured(&buffer_);
-  EmitUint8(0x66);
-  EmitUint8(0x0F);
-  EmitUint8(0x62);
-  EmitXmmRegisterOperand(dst, src);
-}
-
-
 void X86Assembler::addsd(XmmRegister dst, XmmRegister src) {
   AssemblerBuffer::EnsureCapacity ensured(&buffer_);
   EmitUint8(0xF2);
@@ -828,6 +795,51 @@
 }
 
 
+void X86Assembler::paddb(XmmRegister dst, XmmRegister src) {
+  AssemblerBuffer::EnsureCapacity ensured(&buffer_);
+  EmitUint8(0x66);
+  EmitUint8(0x0F);
+  EmitUint8(0xFC);
+  EmitXmmRegisterOperand(dst, src);
+}
+
+
+void X86Assembler::psubb(XmmRegister dst, XmmRegister src) {
+  AssemblerBuffer::EnsureCapacity ensured(&buffer_);
+  EmitUint8(0x66);
+  EmitUint8(0x0F);
+  EmitUint8(0xF8);
+  EmitXmmRegisterOperand(dst, src);
+}
+
+
+void X86Assembler::paddw(XmmRegister dst, XmmRegister src) {
+  AssemblerBuffer::EnsureCapacity ensured(&buffer_);
+  EmitUint8(0x66);
+  EmitUint8(0x0F);
+  EmitUint8(0xFD);
+  EmitXmmRegisterOperand(dst, src);
+}
+
+
+void X86Assembler::psubw(XmmRegister dst, XmmRegister src) {
+  AssemblerBuffer::EnsureCapacity ensured(&buffer_);
+  EmitUint8(0x66);
+  EmitUint8(0x0F);
+  EmitUint8(0xF9);
+  EmitXmmRegisterOperand(dst, src);
+}
+
+
+void X86Assembler::pmullw(XmmRegister dst, XmmRegister src) {
+  AssemblerBuffer::EnsureCapacity ensured(&buffer_);
+  EmitUint8(0x66);
+  EmitUint8(0x0F);
+  EmitUint8(0xD5);
+  EmitXmmRegisterOperand(dst, src);
+}
+
+
 void X86Assembler::paddd(XmmRegister dst, XmmRegister src) {
   AssemblerBuffer::EnsureCapacity ensured(&buffer_);
   EmitUint8(0x66);
@@ -856,6 +868,24 @@
 }
 
 
+void X86Assembler::paddq(XmmRegister dst, XmmRegister src) {
+  AssemblerBuffer::EnsureCapacity ensured(&buffer_);
+  EmitUint8(0x66);
+  EmitUint8(0x0F);
+  EmitUint8(0xD4);
+  EmitXmmRegisterOperand(dst, src);
+}
+
+
+void X86Assembler::psubq(XmmRegister dst, XmmRegister src) {
+  AssemblerBuffer::EnsureCapacity ensured(&buffer_);
+  EmitUint8(0x66);
+  EmitUint8(0x0F);
+  EmitUint8(0xFB);
+  EmitXmmRegisterOperand(dst, src);
+}
+
+
 void X86Assembler::cvtsi2ss(XmmRegister dst, Register src) {
   AssemblerBuffer::EnsureCapacity ensured(&buffer_);
   EmitUint8(0xF3);
@@ -1186,6 +1216,141 @@
 }
 
 
+void X86Assembler::punpcklbw(XmmRegister dst, XmmRegister src) {
+  AssemblerBuffer::EnsureCapacity ensured(&buffer_);
+  EmitUint8(0x66);
+  EmitUint8(0x0F);
+  EmitUint8(0x60);
+  EmitXmmRegisterOperand(dst, src);
+}
+
+
+void X86Assembler::punpcklwd(XmmRegister dst, XmmRegister src) {
+  AssemblerBuffer::EnsureCapacity ensured(&buffer_);
+  EmitUint8(0x66);
+  EmitUint8(0x0F);
+  EmitUint8(0x61);
+  EmitXmmRegisterOperand(dst, src);
+}
+
+
+void X86Assembler::punpckldq(XmmRegister dst, XmmRegister src) {
+  AssemblerBuffer::EnsureCapacity ensured(&buffer_);
+  EmitUint8(0x66);
+  EmitUint8(0x0F);
+  EmitUint8(0x62);
+  EmitXmmRegisterOperand(dst, src);
+}
+
+
+void X86Assembler::punpcklqdq(XmmRegister dst, XmmRegister src) {
+  AssemblerBuffer::EnsureCapacity ensured(&buffer_);
+  EmitUint8(0x66);
+  EmitUint8(0x0F);
+  EmitUint8(0x6C);
+  EmitXmmRegisterOperand(dst, src);
+}
+
+
+void X86Assembler::psllw(XmmRegister reg, const Immediate& shift_count) {
+  DCHECK(shift_count.is_uint8());
+  AssemblerBuffer::EnsureCapacity ensured(&buffer_);
+  EmitUint8(0x66);
+  EmitUint8(0x0F);
+  EmitUint8(0x71);
+  EmitXmmRegisterOperand(6, reg);
+  EmitUint8(shift_count.value());
+}
+
+
+void X86Assembler::pslld(XmmRegister reg, const Immediate& shift_count) {
+  DCHECK(shift_count.is_uint8());
+  AssemblerBuffer::EnsureCapacity ensured(&buffer_);
+  EmitUint8(0x66);
+  EmitUint8(0x0F);
+  EmitUint8(0x72);
+  EmitXmmRegisterOperand(6, reg);
+  EmitUint8(shift_count.value());
+}
+
+
+void X86Assembler::psllq(XmmRegister reg, const Immediate& shift_count) {
+  DCHECK(shift_count.is_uint8());
+  AssemblerBuffer::EnsureCapacity ensured(&buffer_);
+  EmitUint8(0x66);
+  EmitUint8(0x0F);
+  EmitUint8(0x73);
+  EmitXmmRegisterOperand(6, reg);
+  EmitUint8(shift_count.value());
+}
+
+
+void X86Assembler::psraw(XmmRegister reg, const Immediate& shift_count) {
+  DCHECK(shift_count.is_uint8());
+  AssemblerBuffer::EnsureCapacity ensured(&buffer_);
+  EmitUint8(0x66);
+  EmitUint8(0x0F);
+  EmitUint8(0x71);
+  EmitXmmRegisterOperand(4, reg);
+  EmitUint8(shift_count.value());
+}
+
+
+void X86Assembler::psrad(XmmRegister reg, const Immediate& shift_count) {
+  DCHECK(shift_count.is_uint8());
+  AssemblerBuffer::EnsureCapacity ensured(&buffer_);
+  EmitUint8(0x66);
+  EmitUint8(0x0F);
+  EmitUint8(0x72);
+  EmitXmmRegisterOperand(4, reg);
+  EmitUint8(shift_count.value());
+}
+
+
+void X86Assembler::psrlw(XmmRegister reg, const Immediate& shift_count) {
+  DCHECK(shift_count.is_uint8());
+  AssemblerBuffer::EnsureCapacity ensured(&buffer_);
+  EmitUint8(0x66);
+  EmitUint8(0x0F);
+  EmitUint8(0x71);
+  EmitXmmRegisterOperand(2, reg);
+  EmitUint8(shift_count.value());
+}
+
+
+void X86Assembler::psrld(XmmRegister reg, const Immediate& shift_count) {
+  DCHECK(shift_count.is_uint8());
+  AssemblerBuffer::EnsureCapacity ensured(&buffer_);
+  EmitUint8(0x66);
+  EmitUint8(0x0F);
+  EmitUint8(0x72);
+  EmitXmmRegisterOperand(2, reg);
+  EmitUint8(shift_count.value());
+}
+
+
+void X86Assembler::psrlq(XmmRegister reg, const Immediate& shift_count) {
+  DCHECK(shift_count.is_uint8());
+  AssemblerBuffer::EnsureCapacity ensured(&buffer_);
+  EmitUint8(0x66);
+  EmitUint8(0x0F);
+  EmitUint8(0x73);
+  EmitXmmRegisterOperand(2, reg);
+  EmitUint8(shift_count.value());
+}
+
+
+void X86Assembler::psrldq(XmmRegister reg, const Immediate& shift_count) {
+  DCHECK(shift_count.is_uint8());
+  AssemblerBuffer::EnsureCapacity ensured(&buffer_);
+  EmitUint8(0x66);
+  EmitUint8(0x0F);
+  EmitUint8(0x73);
+  EmitXmmRegisterOperand(3, reg);
+  EmitUint8(shift_count.value());
+}
+
+
 void X86Assembler::fldl(const Address& src) {
   AssemblerBuffer::EnsureCapacity ensured(&buffer_);
   EmitUint8(0xDD);
diff --git a/compiler/utils/x86/assembler_x86.h b/compiler/utils/x86/assembler_x86.h
index 4343e2e..2999599 100644
--- a/compiler/utils/x86/assembler_x86.h
+++ b/compiler/utils/x86/assembler_x86.h
@@ -408,14 +408,9 @@
   void movsd(const Address& dst, XmmRegister src);
   void movsd(XmmRegister dst, XmmRegister src);
 
-  void psrlq(XmmRegister reg, const Immediate& shift_count);
-  void punpckldq(XmmRegister dst, XmmRegister src);
-
   void movhpd(XmmRegister dst, const Address& src);
   void movhpd(const Address& dst, XmmRegister src);
 
-  void psrldq(XmmRegister reg, const Immediate& shift_count);
-
   void addsd(XmmRegister dst, XmmRegister src);
   void addsd(XmmRegister dst, const Address& src);
   void subsd(XmmRegister dst, XmmRegister src);
@@ -436,10 +431,20 @@
   void movdqa(const Address& dst, XmmRegister src);  // store aligned
   void movdqu(const Address& dst, XmmRegister src);  // store unaligned
 
-  void paddd(XmmRegister dst, XmmRegister src);  // no addr variant (for now)
+  void paddb(XmmRegister dst, XmmRegister src);  // no addr variant (for now)
+  void psubb(XmmRegister dst, XmmRegister src);
+
+  void paddw(XmmRegister dst, XmmRegister src);
+  void psubw(XmmRegister dst, XmmRegister src);
+  void pmullw(XmmRegister dst, XmmRegister src);
+
+  void paddd(XmmRegister dst, XmmRegister src);
   void psubd(XmmRegister dst, XmmRegister src);
   void pmulld(XmmRegister dst, XmmRegister src);
 
+  void paddq(XmmRegister dst, XmmRegister src);
+  void psubq(XmmRegister dst, XmmRegister src);
+
   void cvtsi2ss(XmmRegister dst, Register src);
   void cvtsi2sd(XmmRegister dst, Register src);
 
@@ -489,6 +494,24 @@
   void shufps(XmmRegister dst, XmmRegister src, const Immediate& imm);
   void pshufd(XmmRegister dst, XmmRegister src, const Immediate& imm);
 
+  void punpcklbw(XmmRegister dst, XmmRegister src);
+  void punpcklwd(XmmRegister dst, XmmRegister src);
+  void punpckldq(XmmRegister dst, XmmRegister src);
+  void punpcklqdq(XmmRegister dst, XmmRegister src);
+
+  void psllw(XmmRegister reg, const Immediate& shift_count);
+  void pslld(XmmRegister reg, const Immediate& shift_count);
+  void psllq(XmmRegister reg, const Immediate& shift_count);
+
+  void psraw(XmmRegister reg, const Immediate& shift_count);
+  void psrad(XmmRegister reg, const Immediate& shift_count);
+  // no psraq
+
+  void psrlw(XmmRegister reg, const Immediate& shift_count);
+  void psrld(XmmRegister reg, const Immediate& shift_count);
+  void psrlq(XmmRegister reg, const Immediate& shift_count);
+  void psrldq(XmmRegister reg, const Immediate& shift_count);
+
   void flds(const Address& src);
   void fstps(const Address& dst);
   void fsts(const Address& dst);
diff --git a/compiler/utils/x86/assembler_x86_test.cc b/compiler/utils/x86/assembler_x86_test.cc
index c6ab893..a74bea2 100644
--- a/compiler/utils/x86/assembler_x86_test.cc
+++ b/compiler/utils/x86/assembler_x86_test.cc
@@ -122,18 +122,6 @@
   DriverStr(expected, "movntl");
 }
 
-TEST_F(AssemblerX86Test, psrlq) {
-  GetAssembler()->psrlq(x86::XMM0, CreateImmediate(32));
-  const char* expected = "psrlq $0x20, %xmm0\n";
-  DriverStr(expected, "psrlq");
-}
-
-TEST_F(AssemblerX86Test, punpckldq) {
-  GetAssembler()->punpckldq(x86::XMM0, x86::XMM1);
-  const char* expected = "punpckldq %xmm1, %xmm0\n";
-  DriverStr(expected, "punpckldq");
-}
-
 TEST_F(AssemblerX86Test, LoadLongConstant) {
   GetAssembler()->LoadLongConstant(x86::XMM0, 51);
   const char* expected =
@@ -521,6 +509,26 @@
   DriverStr(RepeatFF(&x86::X86Assembler::divpd, "divpd %{reg2}, %{reg1}"), "divpd");
 }
 
+TEST_F(AssemblerX86Test, PAddB) {
+  DriverStr(RepeatFF(&x86::X86Assembler::paddb, "paddb %{reg2}, %{reg1}"), "paddb");
+}
+
+TEST_F(AssemblerX86Test, PSubB) {
+  DriverStr(RepeatFF(&x86::X86Assembler::psubb, "psubb %{reg2}, %{reg1}"), "psubb");
+}
+
+TEST_F(AssemblerX86Test, PAddW) {
+  DriverStr(RepeatFF(&x86::X86Assembler::paddw, "paddw %{reg2}, %{reg1}"), "paddw");
+}
+
+TEST_F(AssemblerX86Test, PSubW) {
+  DriverStr(RepeatFF(&x86::X86Assembler::psubw, "psubw %{reg2}, %{reg1}"), "psubw");
+}
+
+TEST_F(AssemblerX86Test, PMullW) {
+  DriverStr(RepeatFF(&x86::X86Assembler::pmullw, "pmullw %{reg2}, %{reg1}"), "pmullw");
+}
+
 TEST_F(AssemblerX86Test, PAddD) {
   DriverStr(RepeatFF(&x86::X86Assembler::paddd, "paddd %{reg2}, %{reg1}"), "paddd");
 }
@@ -533,6 +541,14 @@
   DriverStr(RepeatFF(&x86::X86Assembler::pmulld, "pmulld %{reg2}, %{reg1}"), "pmulld");
 }
 
+TEST_F(AssemblerX86Test, PAddQ) {
+  DriverStr(RepeatFF(&x86::X86Assembler::paddq, "paddq %{reg2}, %{reg1}"), "paddq");
+}
+
+TEST_F(AssemblerX86Test, PSubQ) {
+  DriverStr(RepeatFF(&x86::X86Assembler::psubq, "psubq %{reg2}, %{reg1}"), "psubq");
+}
+
 TEST_F(AssemblerX86Test, XorPD) {
   DriverStr(RepeatFF(&x86::X86Assembler::xorpd, "xorpd %{reg2}, %{reg1}"), "xorpd");
 }
@@ -581,6 +597,67 @@
   DriverStr(RepeatFFI(&x86::X86Assembler::pshufd, 1, "pshufd ${imm}, %{reg2}, %{reg1}"), "pshufd");
 }
 
+TEST_F(AssemblerX86Test, Punpcklbw) {
+  DriverStr(RepeatFF(&x86::X86Assembler::punpcklbw, "punpcklbw %{reg2}, %{reg1}"), "punpcklbw");
+}
+
+TEST_F(AssemblerX86Test, Punpcklwd) {
+  DriverStr(RepeatFF(&x86::X86Assembler::punpcklwd, "punpcklwd %{reg2}, %{reg1}"), "punpcklwd");
+}
+
+TEST_F(AssemblerX86Test, Punpckldq) {
+  DriverStr(RepeatFF(&x86::X86Assembler::punpckldq, "punpckldq %{reg2}, %{reg1}"), "punpckldq");
+}
+
+TEST_F(AssemblerX86Test, Punpcklqdq) {
+  DriverStr(RepeatFF(&x86::X86Assembler::punpcklqdq, "punpcklqdq %{reg2}, %{reg1}"), "punpcklqdq");
+}
+
+TEST_F(AssemblerX86Test, psllw) {
+  GetAssembler()->psllw(x86::XMM0, CreateImmediate(16));
+  DriverStr("psllw $0x10, %xmm0\n", "psllwi");
+}
+
+TEST_F(AssemblerX86Test, pslld) {
+  GetAssembler()->pslld(x86::XMM0, CreateImmediate(16));
+  DriverStr("pslld $0x10, %xmm0\n", "pslldi");
+}
+
+TEST_F(AssemblerX86Test, psllq) {
+  GetAssembler()->psllq(x86::XMM0, CreateImmediate(16));
+  DriverStr("psllq $0x10, %xmm0\n", "psllqi");
+}
+
+TEST_F(AssemblerX86Test, psraw) {
+  GetAssembler()->psraw(x86::XMM0, CreateImmediate(16));
+  DriverStr("psraw $0x10, %xmm0\n", "psrawi");
+}
+
+TEST_F(AssemblerX86Test, psrad) {
+  GetAssembler()->psrad(x86::XMM0, CreateImmediate(16));
+  DriverStr("psrad $0x10, %xmm0\n", "psradi");
+}
+
+TEST_F(AssemblerX86Test, psrlw) {
+  GetAssembler()->psrlw(x86::XMM0, CreateImmediate(16));
+  DriverStr("psrlw $0x10, %xmm0\n", "psrlwi");
+}
+
+TEST_F(AssemblerX86Test, psrld) {
+  GetAssembler()->psrld(x86::XMM0, CreateImmediate(16));
+  DriverStr("psrld $0x10, %xmm0\n", "psrldi");
+}
+
+TEST_F(AssemblerX86Test, psrlq) {
+  GetAssembler()->psrlq(x86::XMM0, CreateImmediate(16));
+  DriverStr("psrlq $0x10, %xmm0\n", "psrlqi");
+}
+
+TEST_F(AssemblerX86Test, psrldq) {
+  GetAssembler()->psrldq(x86::XMM0, CreateImmediate(16));
+  DriverStr("psrldq $0x10, %xmm0\n", "psrldqi");
+}
+
 /////////////////
 // Near labels //
 /////////////////
diff --git a/compiler/utils/x86_64/assembler_x86_64.cc b/compiler/utils/x86_64/assembler_x86_64.cc
index b41be80..458204a 100644
--- a/compiler/utils/x86_64/assembler_x86_64.cc
+++ b/compiler/utils/x86_64/assembler_x86_64.cc
@@ -882,6 +882,56 @@
 }
 
 
+void X86_64Assembler::paddb(XmmRegister dst, XmmRegister src) {
+  AssemblerBuffer::EnsureCapacity ensured(&buffer_);
+  EmitUint8(0x66);
+  EmitOptionalRex32(dst, src);
+  EmitUint8(0x0F);
+  EmitUint8(0xFC);
+  EmitXmmRegisterOperand(dst.LowBits(), src);
+}
+
+
+void X86_64Assembler::psubb(XmmRegister dst, XmmRegister src) {
+  AssemblerBuffer::EnsureCapacity ensured(&buffer_);
+  EmitUint8(0x66);
+  EmitOptionalRex32(dst, src);
+  EmitUint8(0x0F);
+  EmitUint8(0xF8);
+  EmitXmmRegisterOperand(dst.LowBits(), src);
+}
+
+
+void X86_64Assembler::paddw(XmmRegister dst, XmmRegister src) {
+  AssemblerBuffer::EnsureCapacity ensured(&buffer_);
+  EmitUint8(0x66);
+  EmitOptionalRex32(dst, src);
+  EmitUint8(0x0F);
+  EmitUint8(0xFD);
+  EmitXmmRegisterOperand(dst.LowBits(), src);
+}
+
+
+void X86_64Assembler::psubw(XmmRegister dst, XmmRegister src) {
+  AssemblerBuffer::EnsureCapacity ensured(&buffer_);
+  EmitUint8(0x66);
+  EmitOptionalRex32(dst, src);
+  EmitUint8(0x0F);
+  EmitUint8(0xF9);
+  EmitXmmRegisterOperand(dst.LowBits(), src);
+}
+
+
+void X86_64Assembler::pmullw(XmmRegister dst, XmmRegister src) {
+  AssemblerBuffer::EnsureCapacity ensured(&buffer_);
+  EmitUint8(0x66);
+  EmitOptionalRex32(dst, src);
+  EmitUint8(0x0F);
+  EmitUint8(0xD5);
+  EmitXmmRegisterOperand(dst.LowBits(), src);
+}
+
+
 void X86_64Assembler::paddd(XmmRegister dst, XmmRegister src) {
   AssemblerBuffer::EnsureCapacity ensured(&buffer_);
   EmitUint8(0x66);
@@ -913,6 +963,26 @@
 }
 
 
+void X86_64Assembler::paddq(XmmRegister dst, XmmRegister src) {
+  AssemblerBuffer::EnsureCapacity ensured(&buffer_);
+  EmitUint8(0x66);
+  EmitOptionalRex32(dst, src);
+  EmitUint8(0x0F);
+  EmitUint8(0xD4);
+  EmitXmmRegisterOperand(dst.LowBits(), src);
+}
+
+
+void X86_64Assembler::psubq(XmmRegister dst, XmmRegister src) {
+  AssemblerBuffer::EnsureCapacity ensured(&buffer_);
+  EmitUint8(0x66);
+  EmitOptionalRex32(dst, src);
+  EmitUint8(0x0F);
+  EmitUint8(0xFB);
+  EmitXmmRegisterOperand(dst.LowBits(), src);
+}
+
+
 void X86_64Assembler::cvtsi2ss(XmmRegister dst, CpuRegister src) {
   cvtsi2ss(dst, src, false);
 }
@@ -1354,6 +1424,142 @@
 }
 
 
+void X86_64Assembler::punpcklbw(XmmRegister dst, XmmRegister src) {
+  AssemblerBuffer::EnsureCapacity ensured(&buffer_);
+  EmitUint8(0x66);
+  EmitOptionalRex32(dst, src);
+  EmitUint8(0x0F);
+  EmitUint8(0x60);
+  EmitXmmRegisterOperand(dst.LowBits(), src);
+}
+
+
+void X86_64Assembler::punpcklwd(XmmRegister dst, XmmRegister src) {
+  AssemblerBuffer::EnsureCapacity ensured(&buffer_);
+  EmitUint8(0x66);
+  EmitOptionalRex32(dst, src);
+  EmitUint8(0x0F);
+  EmitUint8(0x61);
+  EmitXmmRegisterOperand(dst.LowBits(), src);
+}
+
+
+void X86_64Assembler::punpckldq(XmmRegister dst, XmmRegister src) {
+  AssemblerBuffer::EnsureCapacity ensured(&buffer_);
+  EmitUint8(0x66);
+  EmitOptionalRex32(dst, src);
+  EmitUint8(0x0F);
+  EmitUint8(0x62);
+  EmitXmmRegisterOperand(dst.LowBits(), src);
+}
+
+
+void X86_64Assembler::punpcklqdq(XmmRegister dst, XmmRegister src) {
+  AssemblerBuffer::EnsureCapacity ensured(&buffer_);
+  EmitUint8(0x66);
+  EmitOptionalRex32(dst, src);
+  EmitUint8(0x0F);
+  EmitUint8(0x6C);
+  EmitXmmRegisterOperand(dst.LowBits(), src);
+}
+
+
+void X86_64Assembler::psllw(XmmRegister reg, const Immediate& shift_count) {
+  DCHECK(shift_count.is_uint8());
+  AssemblerBuffer::EnsureCapacity ensured(&buffer_);
+  EmitUint8(0x66);
+  EmitOptionalRex(false, false, false, false, reg.NeedsRex());
+  EmitUint8(0x0F);
+  EmitUint8(0x71);
+  EmitXmmRegisterOperand(6, reg);
+  EmitUint8(shift_count.value());
+}
+
+
+void X86_64Assembler::pslld(XmmRegister reg, const Immediate& shift_count) {
+  DCHECK(shift_count.is_uint8());
+  AssemblerBuffer::EnsureCapacity ensured(&buffer_);
+  EmitUint8(0x66);
+  EmitOptionalRex(false, false, false, false, reg.NeedsRex());
+  EmitUint8(0x0F);
+  EmitUint8(0x72);
+  EmitXmmRegisterOperand(6, reg);
+  EmitUint8(shift_count.value());
+}
+
+
+void X86_64Assembler::psllq(XmmRegister reg, const Immediate& shift_count) {
+  DCHECK(shift_count.is_uint8());
+  AssemblerBuffer::EnsureCapacity ensured(&buffer_);
+  EmitUint8(0x66);
+  EmitOptionalRex(false, false, false, false, reg.NeedsRex());
+  EmitUint8(0x0F);
+  EmitUint8(0x73);
+  EmitXmmRegisterOperand(6, reg);
+  EmitUint8(shift_count.value());
+}
+
+
+void X86_64Assembler::psraw(XmmRegister reg, const Immediate& shift_count) {
+  DCHECK(shift_count.is_uint8());
+  AssemblerBuffer::EnsureCapacity ensured(&buffer_);
+  EmitUint8(0x66);
+  EmitOptionalRex(false, false, false, false, reg.NeedsRex());
+  EmitUint8(0x0F);
+  EmitUint8(0x71);
+  EmitXmmRegisterOperand(4, reg);
+  EmitUint8(shift_count.value());
+}
+
+
+void X86_64Assembler::psrad(XmmRegister reg, const Immediate& shift_count) {
+  DCHECK(shift_count.is_uint8());
+  AssemblerBuffer::EnsureCapacity ensured(&buffer_);
+  EmitUint8(0x66);
+  EmitOptionalRex(false, false, false, false, reg.NeedsRex());
+  EmitUint8(0x0F);
+  EmitUint8(0x72);
+  EmitXmmRegisterOperand(4, reg);
+  EmitUint8(shift_count.value());
+}
+
+
+void X86_64Assembler::psrlw(XmmRegister reg, const Immediate& shift_count) {
+  DCHECK(shift_count.is_uint8());
+  AssemblerBuffer::EnsureCapacity ensured(&buffer_);
+  EmitUint8(0x66);
+  EmitOptionalRex(false, false, false, false, reg.NeedsRex());
+  EmitUint8(0x0F);
+  EmitUint8(0x71);
+  EmitXmmRegisterOperand(2, reg);
+  EmitUint8(shift_count.value());
+}
+
+
+void X86_64Assembler::psrld(XmmRegister reg, const Immediate& shift_count) {
+  DCHECK(shift_count.is_uint8());
+  AssemblerBuffer::EnsureCapacity ensured(&buffer_);
+  EmitUint8(0x66);
+  EmitOptionalRex(false, false, false, false, reg.NeedsRex());
+  EmitUint8(0x0F);
+  EmitUint8(0x72);
+  EmitXmmRegisterOperand(2, reg);
+  EmitUint8(shift_count.value());
+}
+
+
+void X86_64Assembler::psrlq(XmmRegister reg, const Immediate& shift_count) {
+  DCHECK(shift_count.is_uint8());
+  AssemblerBuffer::EnsureCapacity ensured(&buffer_);
+  EmitUint8(0x66);
+  EmitOptionalRex(false, false, false, false, reg.NeedsRex());
+  EmitUint8(0x0F);
+  EmitUint8(0x73);
+  EmitXmmRegisterOperand(2, reg);
+  EmitUint8(shift_count.value());
+}
+
+
 void X86_64Assembler::fldl(const Address& src) {
   AssemblerBuffer::EnsureCapacity ensured(&buffer_);
   EmitUint8(0xDD);
diff --git a/compiler/utils/x86_64/assembler_x86_64.h b/compiler/utils/x86_64/assembler_x86_64.h
index 43ea12a..0dc11d8 100644
--- a/compiler/utils/x86_64/assembler_x86_64.h
+++ b/compiler/utils/x86_64/assembler_x86_64.h
@@ -452,10 +452,20 @@
   void movdqa(const Address& dst, XmmRegister src);  // store aligned
   void movdqu(const Address& dst, XmmRegister src);  // store unaligned
 
-  void paddd(XmmRegister dst, XmmRegister src);  // no addr variant (for now)
+  void paddb(XmmRegister dst, XmmRegister src);  // no addr variant (for now)
+  void psubb(XmmRegister dst, XmmRegister src);
+
+  void paddw(XmmRegister dst, XmmRegister src);
+  void psubw(XmmRegister dst, XmmRegister src);
+  void pmullw(XmmRegister dst, XmmRegister src);
+
+  void paddd(XmmRegister dst, XmmRegister src);
   void psubd(XmmRegister dst, XmmRegister src);
   void pmulld(XmmRegister dst, XmmRegister src);
 
+  void paddq(XmmRegister dst, XmmRegister src);
+  void psubq(XmmRegister dst, XmmRegister src);
+
   void cvtsi2ss(XmmRegister dst, CpuRegister src);  // Note: this is the r/m32 version.
   void cvtsi2ss(XmmRegister dst, CpuRegister src, bool is64bit);
   void cvtsi2ss(XmmRegister dst, const Address& src, bool is64bit);
@@ -512,6 +522,23 @@
   void shufps(XmmRegister dst, XmmRegister src, const Immediate& imm);
   void pshufd(XmmRegister dst, XmmRegister src, const Immediate& imm);
 
+  void punpcklbw(XmmRegister dst, XmmRegister src);
+  void punpcklwd(XmmRegister dst, XmmRegister src);
+  void punpckldq(XmmRegister dst, XmmRegister src);
+  void punpcklqdq(XmmRegister dst, XmmRegister src);
+
+  void psllw(XmmRegister reg, const Immediate& shift_count);
+  void pslld(XmmRegister reg, const Immediate& shift_count);
+  void psllq(XmmRegister reg, const Immediate& shift_count);
+
+  void psraw(XmmRegister reg, const Immediate& shift_count);
+  void psrad(XmmRegister reg, const Immediate& shift_count);
+  // no psraq
+
+  void psrlw(XmmRegister reg, const Immediate& shift_count);
+  void psrld(XmmRegister reg, const Immediate& shift_count);
+  void psrlq(XmmRegister reg, const Immediate& shift_count);
+
   void flds(const Address& src);
   void fstps(const Address& dst);
   void fsts(const Address& dst);
diff --git a/compiler/utils/x86_64/assembler_x86_64_test.cc b/compiler/utils/x86_64/assembler_x86_64_test.cc
index aeb1911..fe94497 100644
--- a/compiler/utils/x86_64/assembler_x86_64_test.cc
+++ b/compiler/utils/x86_64/assembler_x86_64_test.cc
@@ -1128,6 +1128,26 @@
   DriverStr(RepeatFF(&x86_64::X86_64Assembler::divpd, "divpd %{reg2}, %{reg1}"), "divpd");
 }
 
+TEST_F(AssemblerX86_64Test, Paddb) {
+  DriverStr(RepeatFF(&x86_64::X86_64Assembler::paddb, "paddb %{reg2}, %{reg1}"), "paddb");
+}
+
+TEST_F(AssemblerX86_64Test, Psubb) {
+  DriverStr(RepeatFF(&x86_64::X86_64Assembler::psubb, "psubb %{reg2}, %{reg1}"), "psubb");
+}
+
+TEST_F(AssemblerX86_64Test, Paddw) {
+  DriverStr(RepeatFF(&x86_64::X86_64Assembler::paddw, "paddw %{reg2}, %{reg1}"), "paddw");
+}
+
+TEST_F(AssemblerX86_64Test, Psubw) {
+  DriverStr(RepeatFF(&x86_64::X86_64Assembler::psubw, "psubw %{reg2}, %{reg1}"), "psubw");
+}
+
+TEST_F(AssemblerX86_64Test, Pmullw) {
+  DriverStr(RepeatFF(&x86_64::X86_64Assembler::pmullw, "pmullw %{reg2}, %{reg1}"), "pmullw");
+}
+
 TEST_F(AssemblerX86_64Test, Paddd) {
   DriverStr(RepeatFF(&x86_64::X86_64Assembler::paddd, "paddd %{reg2}, %{reg1}"), "paddd");
 }
@@ -1140,6 +1160,14 @@
   DriverStr(RepeatFF(&x86_64::X86_64Assembler::pmulld, "pmulld %{reg2}, %{reg1}"), "pmulld");
 }
 
+TEST_F(AssemblerX86_64Test, Paddq) {
+  DriverStr(RepeatFF(&x86_64::X86_64Assembler::paddq, "paddq %{reg2}, %{reg1}"), "paddq");
+}
+
+TEST_F(AssemblerX86_64Test, Psubq) {
+  DriverStr(RepeatFF(&x86_64::X86_64Assembler::psubq, "psubq %{reg2}, %{reg1}"), "psubq");
+}
+
 TEST_F(AssemblerX86_64Test, Cvtsi2ss) {
   DriverStr(RepeatFr(&x86_64::X86_64Assembler::cvtsi2ss, "cvtsi2ss %{reg2}, %{reg1}"), "cvtsi2ss");
 }
@@ -1261,6 +1289,78 @@
   DriverStr(RepeatFFI(&x86_64::X86_64Assembler::pshufd, 1, "pshufd ${imm}, %{reg2}, %{reg1}"), "pshufd");
 }
 
+TEST_F(AssemblerX86_64Test, Punpcklbw) {
+  DriverStr(RepeatFF(&x86_64::X86_64Assembler::punpcklbw, "punpcklbw %{reg2}, %{reg1}"), "punpcklbw");
+}
+
+TEST_F(AssemblerX86_64Test, Punpcklwd) {
+  DriverStr(RepeatFF(&x86_64::X86_64Assembler::punpcklwd, "punpcklwd %{reg2}, %{reg1}"), "punpcklwd");
+}
+
+TEST_F(AssemblerX86_64Test, Punpckldq) {
+  DriverStr(RepeatFF(&x86_64::X86_64Assembler::punpckldq, "punpckldq %{reg2}, %{reg1}"), "punpckldq");
+}
+
+TEST_F(AssemblerX86_64Test, Punpcklqdq) {
+  DriverStr(RepeatFF(&x86_64::X86_64Assembler::punpcklqdq, "punpcklqdq %{reg2}, %{reg1}"), "punpcklqdq");
+}
+
+TEST_F(AssemblerX86_64Test, Psllw) {
+  GetAssembler()->psllw(x86_64::XmmRegister(x86_64::XMM0),  x86_64::Immediate(1));
+  GetAssembler()->psllw(x86_64::XmmRegister(x86_64::XMM15), x86_64::Immediate(2));
+  DriverStr("psllw $1, %xmm0\n"
+            "psllw $2, %xmm15\n", "psllwi");
+}
+
+TEST_F(AssemblerX86_64Test, Pslld) {
+  GetAssembler()->pslld(x86_64::XmmRegister(x86_64::XMM0),  x86_64::Immediate(1));
+  GetAssembler()->pslld(x86_64::XmmRegister(x86_64::XMM15), x86_64::Immediate(2));
+  DriverStr("pslld $1, %xmm0\n"
+            "pslld $2, %xmm15\n", "pslldi");
+}
+
+TEST_F(AssemblerX86_64Test, Psllq) {
+  GetAssembler()->psllq(x86_64::XmmRegister(x86_64::XMM0),  x86_64::Immediate(1));
+  GetAssembler()->psllq(x86_64::XmmRegister(x86_64::XMM15), x86_64::Immediate(2));
+  DriverStr("psllq $1, %xmm0\n"
+            "psllq $2, %xmm15\n", "psllqi");
+}
+
+TEST_F(AssemblerX86_64Test, Psraw) {
+  GetAssembler()->psraw(x86_64::XmmRegister(x86_64::XMM0),  x86_64::Immediate(1));
+  GetAssembler()->psraw(x86_64::XmmRegister(x86_64::XMM15), x86_64::Immediate(2));
+  DriverStr("psraw $1, %xmm0\n"
+            "psraw $2, %xmm15\n", "psrawi");
+}
+
+TEST_F(AssemblerX86_64Test, Psrad) {
+  GetAssembler()->psrad(x86_64::XmmRegister(x86_64::XMM0),  x86_64::Immediate(1));
+  GetAssembler()->psrad(x86_64::XmmRegister(x86_64::XMM15), x86_64::Immediate(2));
+  DriverStr("psrad $1, %xmm0\n"
+            "psrad $2, %xmm15\n", "psradi");
+}
+
+TEST_F(AssemblerX86_64Test, Psrlw) {
+  GetAssembler()->psrlw(x86_64::XmmRegister(x86_64::XMM0),  x86_64::Immediate(1));
+  GetAssembler()->psrlw(x86_64::XmmRegister(x86_64::XMM15), x86_64::Immediate(2));
+  DriverStr("psrlw $1, %xmm0\n"
+            "psrlw $2, %xmm15\n", "psrlwi");
+}
+
+TEST_F(AssemblerX86_64Test, Psrld) {
+  GetAssembler()->psrld(x86_64::XmmRegister(x86_64::XMM0),  x86_64::Immediate(1));
+  GetAssembler()->psrld(x86_64::XmmRegister(x86_64::XMM15), x86_64::Immediate(2));
+  DriverStr("psrld $1, %xmm0\n"
+            "psrld $2, %xmm15\n", "pslldi");
+}
+
+TEST_F(AssemblerX86_64Test, Psrlq) {
+  GetAssembler()->psrlq(x86_64::XmmRegister(x86_64::XMM0),  x86_64::Immediate(1));
+  GetAssembler()->psrlq(x86_64::XmmRegister(x86_64::XMM15), x86_64::Immediate(2));
+  DriverStr("psrlq $1, %xmm0\n"
+            "psrlq $2, %xmm15\n", "pslrqi");
+}
+
 TEST_F(AssemblerX86_64Test, UcomissAddress) {
   GetAssembler()->ucomiss(x86_64::XmmRegister(x86_64::XMM0), x86_64::Address(
       x86_64::CpuRegister(x86_64::RDI), x86_64::CpuRegister(x86_64::RBX), x86_64::TIMES_4, 12));
diff --git a/dex2oat/dex2oat.cc b/dex2oat/dex2oat.cc
index 026a567..be75628 100644
--- a/dex2oat/dex2oat.cc
+++ b/dex2oat/dex2oat.cc
@@ -424,7 +424,13 @@
         shutting_down_(false) {
     const char* reason = "dex2oat watch dog thread startup";
     CHECK_WATCH_DOG_PTHREAD_CALL(pthread_mutex_init, (&mutex_, nullptr), reason);
-    CHECK_WATCH_DOG_PTHREAD_CALL(pthread_cond_init, (&cond_, nullptr), reason);
+#ifndef __APPLE__
+    pthread_condattr_t condattr;
+    CHECK_WATCH_DOG_PTHREAD_CALL(pthread_condattr_init, (&condattr), reason);
+    CHECK_WATCH_DOG_PTHREAD_CALL(pthread_condattr_setclock, (&condattr, CLOCK_MONOTONIC), reason);
+    CHECK_WATCH_DOG_PTHREAD_CALL(pthread_cond_init, (&cond_, &condattr), reason);
+    CHECK_WATCH_DOG_PTHREAD_CALL(pthread_condattr_destroy, (&condattr), reason);
+#endif
     CHECK_WATCH_DOG_PTHREAD_CALL(pthread_attr_init, (&attr_), reason);
     CHECK_WATCH_DOG_PTHREAD_CALL(pthread_create, (&pthread_, &attr_, &CallBack, this), reason);
     CHECK_WATCH_DOG_PTHREAD_CALL(pthread_attr_destroy, (&attr_), reason);
@@ -482,7 +488,11 @@
 
   void Wait() {
     timespec timeout_ts;
+#if defined(__APPLE__)
     InitTimeSpec(true, CLOCK_REALTIME, timeout_in_milliseconds_, 0, &timeout_ts);
+#else
+    InitTimeSpec(true, CLOCK_MONOTONIC, timeout_in_milliseconds_, 0, &timeout_ts);
+#endif
     const char* reason = "dex2oat watch dog thread waiting";
     CHECK_WATCH_DOG_PTHREAD_CALL(pthread_mutex_lock, (&mutex_), reason);
     while (!shutting_down_) {
@@ -1541,10 +1551,10 @@
         std::unique_ptr<MemMap> opened_dex_files_map;
         std::vector<std::unique_ptr<const DexFile>> opened_dex_files;
         // No need to verify the dex file for:
-        // 1) kSpeedProfile, since it includes dexlayout, which does the verification.
+        // 1) Dexlayout since it does the verification. It also may not pass the verification since
+        // we don't update the dex checksum.
         // 2) when we have a vdex file, which means it was already verified.
-        bool verify = compiler_options_->GetCompilerFilter() != CompilerFilter::kSpeedProfile &&
-            (input_vdex_file_ == nullptr);
+        const bool verify = !DoDexLayoutOptimizations() && (input_vdex_file_ == nullptr);
         if (!oat_writers_[i]->WriteAndOpenDexFiles(
             kIsVdexEnabled ? vdex_files_[i].get() : oat_files_[i].get(),
             rodata_.back(),
@@ -2094,12 +2104,20 @@
     return is_host_;
   }
 
-  bool UseProfileGuidedCompilation() const {
-    return CompilerFilter::DependsOnProfile(compiler_options_->GetCompilerFilter());
+  bool UseProfile() const {
+    return profile_file_fd_ != -1 || !profile_file_.empty();
+  }
+
+  bool DoProfileGuidedOptimizations() const {
+    return UseProfile() && compiler_options_->GetCompilerFilter() != CompilerFilter::kVerifyProfile;
+  }
+
+  bool DoDexLayoutOptimizations() const {
+    return DoProfileGuidedOptimizations();
   }
 
   bool LoadProfile() {
-    DCHECK(UseProfileGuidedCompilation());
+    DCHECK(UseProfile());
 
     profile_compilation_info_.reset(new ProfileCompilationInfo());
     ScopedFlock flock;
@@ -2356,7 +2374,7 @@
                                                      compiler_options_.get(),
                                                      oat_file.get()));
       elf_writers_.back()->Start();
-      bool do_dexlayout = compiler_options_->GetCompilerFilter() == CompilerFilter::kSpeedProfile;
+      const bool do_dexlayout = DoDexLayoutOptimizations();
       oat_writers_.emplace_back(new OatWriter(
           IsBootImage(), timings_, do_dexlayout ? profile_compilation_info_.get() : nullptr));
     }
@@ -2873,7 +2891,7 @@
 
   // If needed, process profile information for profile guided compilation.
   // This operation involves I/O.
-  if (dex2oat->UseProfileGuidedCompilation()) {
+  if (dex2oat->UseProfile()) {
     if (!dex2oat->LoadProfile()) {
       LOG(ERROR) << "Failed to process profile file";
       return EXIT_FAILURE;
diff --git a/dex2oat/dex2oat_test.cc b/dex2oat/dex2oat_test.cc
index 6881f75..2c0b125 100644
--- a/dex2oat/dex2oat_test.cc
+++ b/dex2oat/dex2oat_test.cc
@@ -554,6 +554,12 @@
   RunTest(CompilerFilter::kSpeed, true, { "--very-large-app-threshold=100" });
 }
 
+// Regressin test for b/35665292.
+TEST_F(Dex2oatVeryLargeTest, SpeedProfileNoProfile) {
+  // Test that dex2oat doesn't crash with speed-profile but no input profile.
+  RunTest(CompilerFilter::kSpeedProfile, false);
+}
+
 class Dex2oatLayoutTest : public Dex2oatTest {
  protected:
   void CheckFilter(CompilerFilter::Filter input ATTRIBUTE_UNUSED,
diff --git a/dexlayout/dex_ir.cc b/dexlayout/dex_ir.cc
index 2d9bbfd..609068f 100644
--- a/dexlayout/dex_ir.cc
+++ b/dexlayout/dex_ir.cc
@@ -616,6 +616,7 @@
       for (std::unique_ptr<const CatchHandler>& existing_handlers : *handler_list) {
         if (handler_off == existing_handlers->GetListOffset()) {
           handlers = existing_handlers.get();
+          break;
         }
       }
       if (handlers == nullptr) {
@@ -634,7 +635,51 @@
       TryItem* try_item = new TryItem(start_addr, insn_count, handlers);
       tries->push_back(std::unique_ptr<const TryItem>(try_item));
     }
+    // Manually walk catch handlers list and add any missing handlers unreferenced by try items.
+    const uint8_t* handlers_base = DexFile::GetCatchHandlerData(disk_code_item, 0);
+    const uint8_t* handlers_data = handlers_base;
+    uint32_t handlers_size = DecodeUnsignedLeb128(&handlers_data);
+    while (handlers_size > handler_list->size()) {
+      bool already_added = false;
+      uint16_t handler_off = handlers_data - handlers_base;
+      for (std::unique_ptr<const CatchHandler>& existing_handlers : *handler_list) {
+        if (handler_off == existing_handlers->GetListOffset()) {
+          already_added = true;
+          break;
+        }
+      }
+      int32_t size = DecodeSignedLeb128(&handlers_data);
+      bool has_catch_all = size < 0;
+      if (has_catch_all) {
+        size = -size;
+      }
+      if (already_added == true)  {
+        for (int32_t i = 0; i < size; i++) {
+          DecodeUnsignedLeb128(&handlers_data);
+          DecodeUnsignedLeb128(&handlers_data);
+        }
+        if (has_catch_all) {
+          DecodeUnsignedLeb128(&handlers_data);
+        }
+        continue;
+      }
+      TypeAddrPairVector* addr_pairs = new TypeAddrPairVector();
+      for (int32_t i = 0; i < size; i++) {
+        const TypeId* type_id = GetTypeIdOrNullPtr(DecodeUnsignedLeb128(&handlers_data));
+        uint32_t addr = DecodeUnsignedLeb128(&handlers_data);
+        addr_pairs->push_back(
+            std::unique_ptr<const TypeAddrPair>(new TypeAddrPair(type_id, addr)));
+      }
+      if (has_catch_all) {
+        uint32_t addr = DecodeUnsignedLeb128(&handlers_data);
+        addr_pairs->push_back(
+            std::unique_ptr<const TypeAddrPair>(new TypeAddrPair(nullptr, addr)));
+      }
+      const CatchHandler* handler = new CatchHandler(has_catch_all, handler_off, addr_pairs);
+      handler_list->push_back(std::unique_ptr<const CatchHandler>(handler));
+    }
   }
+
   uint32_t size = GetCodeItemSize(dex_file, disk_code_item);
   CodeItem* code_item = new CodeItem(
       registers_size, ins_size, outs_size, debug_info, insns_size, insns, tries, handler_list);
diff --git a/dexlayout/dexlayout_test.cc b/dexlayout/dexlayout_test.cc
index da1e1d2..9881e28 100644
--- a/dexlayout/dexlayout_test.cc
+++ b/dexlayout/dexlayout_test.cc
@@ -41,7 +41,7 @@
     "AAAAdQEAAAAQAAABAAAAjAEAAA==";
 
 static const char kDexFileLayoutInputProfile[] =
-    "cHJvADAwMgABAAsAAAABAPUpbf5jbGFzc2VzLmRleAEA";
+    "cHJvADAwMwABCwABAAAAAAD1KW3+Y2xhc3Nlcy5kZXgBAA==";
 
 static const char kDexFileLayoutExpectedOutputDex[] =
     "ZGV4CjAzNQD1KW3+B8NAB0f2A/ZVIBJ0aHrGIqcpVTAUAgAAcAAAAHhWNBIAAAAAAAAAAIwBAAAH"
@@ -55,19 +55,87 @@
     "qAAAAAYAAAACAAAAwAAAAAEgAAACAAAAAAEAAAIgAAAHAAAAMAEAAAMgAAACAAAAaQEAAAAgAAAC"
     "AAAAdQEAAAAQAAABAAAAjAEAAA==";
 
-static void WriteFileBase64(const char* base64, const char* location) {
+// Dex file with multiple code items that have the same debug_info_off_. Constructed by a modified
+// dexlayout on XandY.
+static const char kDexFileDuplicateOffset[] =
+    "ZGV4CjAzNwAQfXfPCB8qCxo7MqdFhmHZQwCv8+udHD8MBAAAcAAAAHhWNBIAAAAAAAAAAFQDAAAT"
+    "AAAAcAAAAAgAAAC8AAAAAQAAANwAAAABAAAA6AAAAAUAAADwAAAAAwAAABgBAACUAgAAeAEAABQC"
+    "AAAeAgAAJgIAACsCAAAyAgAANwIAAFsCAAB7AgAAngIAALICAAC1AgAAvQIAAMUCAADIAgAA1QIA"
+    "AOkCAADvAgAA9QIAAPwCAAACAAAAAwAAAAQAAAAFAAAABgAAAAcAAAAIAAAACQAAAAkAAAAHAAAA"
+    "AAAAAAIAAQASAAAAAAAAAAEAAAABAAAAAQAAAAIAAAAAAAAAAgAAAAEAAAAGAAAAAQAAAAAAAAAA"
+    "AAAABgAAAAAAAAAKAAAAAAAAACsDAAAAAAAAAQAAAAAAAAAGAAAAAAAAAAsAAAD0AQAANQMAAAAA"
+    "AAACAAAAAAAAAAAAAAAAAAAACwAAAAQCAAA/AwAAAAAAAAIAAAAUAwAAGgMAAAEAAAAjAwAAAQAB"
+    "AAEAAAAFAAAABAAAAHAQBAAAAA4AAQABAAEAAAAFAAAABAAAAHAQBAAAAA4AAQAAAAEAAAAFAAAA"
+    "CAAAACIAAQBwEAEAAABpAAAADgABAAEAAQAAAAUAAAAEAAAAcBAAAAAADgB4AQAAAAAAAAAAAAAA"
+    "AAAAhAEAAAAAAAAAAAAAAAAAAAg8Y2xpbml0PgAGPGluaXQ+AANMWDsABUxZJFo7AANMWTsAIkxk"
+    "YWx2aWsvYW5ub3RhdGlvbi9FbmNsb3NpbmdDbGFzczsAHkxkYWx2aWsvYW5ub3RhdGlvbi9Jbm5l"
+    "ckNsYXNzOwAhTGRhbHZpay9hbm5vdGF0aW9uL01lbWJlckNsYXNzZXM7ABJMamF2YS9sYW5nL09i"
+    "amVjdDsAAVYABlguamF2YQAGWS5qYXZhAAFaAAthY2Nlc3NGbGFncwASZW1pdHRlcjogamFjay00"
+    "LjI1AARuYW1lAAR0aGlzAAV2YWx1ZQABegARAAcOABMABw4AEgAHDnYAEQAHDgACAwERGAICBAIN"
+    "BAgPFwwCBQERHAEYAQAAAQAAgIAEjAMAAAEAAYCABKQDAQACAAAIAoiABLwDAYCABNwDAAAADwAA"
+    "AAAAAAABAAAAAAAAAAEAAAATAAAAcAAAAAIAAAAIAAAAvAAAAAMAAAABAAAA3AAAAAQAAAABAAAA"
+    "6AAAAAUAAAAFAAAA8AAAAAYAAAADAAAAGAEAAAMQAAACAAAAeAEAAAEgAAAEAAAAjAEAAAYgAAAC"
+    "AAAA9AEAAAIgAAATAAAAFAIAAAMgAAAEAAAA/wIAAAQgAAADAAAAFAMAAAAgAAADAAAAKwMAAAAQ"
+    "AAABAAAAVAMAAA==";
+
+// Dex file with null value for annotations_off in the annotation_set_ref_list.
+// Constructed by building a dex file with annotations and hex editing.
+static const char kNullSetRefListElementInputDex[] =
+    "ZGV4CjAzNQB1iA+7ZwgkF+7E6ZesYFc2lRAR3qnRAanwAwAAcAAAAHhWNBIAAAAAAAAAACADAAAS"
+    "AAAAcAAAAAgAAAC4AAAAAwAAANgAAAABAAAA/AAAAAQAAAAEAQAAAgAAACQBAACMAgAAZAEAAOgB"
+    "AADwAQAAAAIAAAMCAAAQAgAAIAIAADQCAABIAgAAawIAAI0CAAC1AgAAyAIAANECAADUAgAA2QIA"
+    "ANwCAADjAgAA6QIAAAMAAAAEAAAABQAAAAYAAAAHAAAACAAAAAkAAAAMAAAAAgAAAAMAAAAAAAAA"
+    "DAAAAAcAAAAAAAAADQAAAAcAAADgAQAABgAGAAsAAAAAAAEAAAAAAAAAAgAOAAAAAQAAABAAAAAC"
+    "AAEAAAAAAAAAAAAAAAAAAgAAAAAAAAABAAAAsAEAAAgDAAAAAAAAAQAAAAEmAAACAAAA2AEAAAoA"
+    "AADIAQAAFgMAAAAAAAACAAAAAAAAAHwBAAABAAAA/AIAAAAAAAABAAAAAgMAAAEAAQABAAAA8AIA"
+    "AAQAAABwEAMAAAAOAAIAAgAAAAAA9QIAAAEAAAAOAAAAAAAAAAAAAAAAAAAAAQAAAAEAAABkAQAA"
+    "cAEAAAAAAAAAAAAAAAAAAAEAAAAEAAAAAgAAAAMAAwAGPGluaXQ+AA5Bbm5vQ2xhc3MuamF2YQAB"
+    "TAALTEFubm9DbGFzczsADkxNeUFubm90YXRpb247ABJMamF2YS9sYW5nL09iamVjdDsAEkxqYXZh"
+    "L2xhbmcvU3RyaW5nOwAhTGphdmEvbGFuZy9hbm5vdGF0aW9uL0Fubm90YXRpb247ACBMamF2YS9s"
+    "YW5nL2Fubm90YXRpb24vUmV0ZW50aW9uOwAmTGphdmEvbGFuZy9hbm5vdGF0aW9uL1JldGVudGlv"
+    "blBvbGljeTsAEU15QW5ub3RhdGlvbi5qYXZhAAdSVU5USU1FAAFWAANWTEwAAWEABWFOYW1lAARu"
+    "YW1lAAV2YWx1ZQABAAcOAAICAAAHDgABBQERGwABAQEQFw8AAAIAAICABIQDAQmcAwAAAAECgQgA"
+    "AAARAAAAAAAAAAEAAAAAAAAAAQAAABIAAABwAAAAAgAAAAgAAAC4AAAAAwAAAAMAAADYAAAABAAA"
+    "AAEAAAD8AAAABQAAAAQAAAAEAQAABgAAAAIAAAAkAQAAAhAAAAEAAABkAQAAAxAAAAMAAABwAQAA"
+    "ASAAAAIAAACEAQAABiAAAAIAAACwAQAAARAAAAIAAADYAQAAAiAAABIAAADoAQAAAyAAAAIAAADw"
+    "AgAABCAAAAIAAAD8AgAAACAAAAIAAAAIAwAAABAAAAEAAAAgAwAA";
+
+// Dex file with catch handler unreferenced by try blocks.
+// Constructed by building a dex file with try/catch blocks and hex editing.
+static const char kUnreferencedCatchHandlerInputDex[] =
+    "ZGV4CjAzNQD+exd52Y0f9nY5x5GmInXq5nXrO6Kl2RV4AwAAcAAAAHhWNBIAAAAAAAAAANgCAAAS"
+    "AAAAcAAAAAgAAAC4AAAAAwAAANgAAAABAAAA/AAAAAQAAAAEAQAAAQAAACQBAAA0AgAARAEAANYB"
+    "AADeAQAA5gEAAO4BAAAAAgAADwIAACYCAAA9AgAAUQIAAGUCAAB5AgAAfwIAAIUCAACIAgAAjAIA"
+    "AKECAACnAgAArAIAAAQAAAAFAAAABgAAAAcAAAAIAAAACQAAAAwAAAAOAAAADAAAAAYAAAAAAAAA"
+    "DQAAAAYAAADIAQAADQAAAAYAAADQAQAABQABABAAAAAAAAAAAAAAAAAAAgAPAAAAAQABABEAAAAD"
+    "AAAAAAAAAAAAAAABAAAAAwAAAAAAAAADAAAAAAAAAMgCAAAAAAAAAQABAAEAAAC1AgAABAAAAHAQ"
+    "AwAAAA4AAwABAAIAAgC6AgAAIQAAAGIAAAAaAQoAbiACABAAYgAAABoBCwBuIAIAEAAOAA0AYgAA"
+    "ABoBAQBuIAIAEAAo8A0AYgAAABoBAgBuIAIAEAAo7gAAAAAAAAcAAQAHAAAABwABAAIBAg8BAhgA"
+    "AQAAAAQAAAABAAAABwAGPGluaXQ+AAZDYXRjaDEABkNhdGNoMgAQSGFuZGxlclRlc3QuamF2YQAN"
+    "TEhhbmRsZXJUZXN0OwAVTGphdmEvaW8vUHJpbnRTdHJlYW07ABVMamF2YS9sYW5nL0V4Y2VwdGlv"
+    "bjsAEkxqYXZhL2xhbmcvT2JqZWN0OwASTGphdmEvbGFuZy9TdHJpbmc7ABJMamF2YS9sYW5nL1N5"
+    "c3RlbTsABFRyeTEABFRyeTIAAVYAAlZMABNbTGphdmEvbGFuZy9TdHJpbmc7AARtYWluAANvdXQA"
+    "B3ByaW50bG4AAQAHDgAEAQAHDn17AncdHoseAAAAAgAAgYAExAIBCdwCAAANAAAAAAAAAAEAAAAA"
+    "AAAAAQAAABIAAABwAAAAAgAAAAgAAAC4AAAAAwAAAAMAAADYAAAABAAAAAEAAAD8AAAABQAAAAQA"
+    "AAAEAQAABgAAAAEAAAAkAQAAASAAAAIAAABEAQAAARAAAAIAAADIAQAAAiAAABIAAADWAQAAAyAA"
+    "AAIAAAC1AgAAACAAAAEAAADIAgAAABAAAAEAAADYAgAA";
+
+static void WriteBase64ToFile(const char* base64, File* file) {
   // Decode base64.
   CHECK(base64 != nullptr);
   size_t length;
   std::unique_ptr<uint8_t[]> bytes(DecodeBase64(base64, &length));
-  CHECK(bytes.get() != nullptr);
-
-  // Write to provided file.
-  std::unique_ptr<File> file(OS::CreateEmptyFile(location));
-  CHECK(file.get() != nullptr);
+  CHECK(bytes != nullptr);
   if (!file->WriteFully(bytes.get(), length)) {
     PLOG(FATAL) << "Failed to write base64 as file";
   }
+}
+
+static void WriteFileBase64(const char* base64, const char* location) {
+  // Write to provided file.
+  std::unique_ptr<File> file(OS::CreateEmptyFile(location));
+  CHECK(file != nullptr);
+  WriteBase64ToFile(base64, file.get());
   if (file->FlushCloseOrErase() != 0) {
     PLOG(FATAL) << "Could not flush and close test file.";
   }
@@ -171,7 +239,7 @@
     EXPECT_TRUE(OS::FileExists(dexlayout.c_str())) << dexlayout << " should be a valid file path";
 
     std::vector<std::string> dexlayout_exec_argv =
-    { dexlayout, "-w", tmp_dir, "-o", tmp_name, "-p", profile_file, dex_file };
+        { dexlayout, "-w", tmp_dir, "-o", tmp_name, "-p", profile_file, dex_file };
     if (!::art::Exec(dexlayout_exec_argv, error_msg)) {
       return false;
     }
@@ -188,6 +256,40 @@
     }
     return true;
   }
+
+  // Runs UnreferencedCatchHandlerTest.
+  bool UnreferencedCatchHandlerExec(std::string* error_msg) {
+    ScratchFile tmp_file;
+    std::string tmp_name = tmp_file.GetFilename();
+    size_t tmp_last_slash = tmp_name.rfind("/");
+    std::string tmp_dir = tmp_name.substr(0, tmp_last_slash + 1);
+
+    // Write inputs and expected outputs.
+    std::string input_dex = tmp_dir + "classes.dex";
+    WriteFileBase64(kUnreferencedCatchHandlerInputDex, input_dex.c_str());
+    std::string output_dex = tmp_dir + "classes.dex.new";
+
+    std::string dexlayout = GetTestAndroidRoot() + "/bin/dexlayout";
+    EXPECT_TRUE(OS::FileExists(dexlayout.c_str())) << dexlayout << " should be a valid file path";
+
+    std::vector<std::string> dexlayout_exec_argv =
+        { dexlayout, "-w", tmp_dir, "-o", "/dev/null", input_dex };
+    if (!::art::Exec(dexlayout_exec_argv, error_msg)) {
+      return false;
+    }
+
+    // Diff input and output. They should be the same.
+    std::vector<std::string> diff_exec_argv = { "/usr/bin/diff", input_dex, output_dex };
+    if (!::art::Exec(diff_exec_argv, error_msg)) {
+      return false;
+    }
+
+    std::vector<std::string> rm_exec_argv = { "/bin/rm", input_dex, output_dex };
+    if (!::art::Exec(rm_exec_argv, error_msg)) {
+      return false;
+    }
+    return true;
+  }
 };
 
 
@@ -212,4 +314,48 @@
   ASSERT_TRUE(DexFileLayoutExec(&error_msg)) << error_msg;
 }
 
+TEST_F(DexLayoutTest, DuplicateOffset) {
+  ScratchFile temp;
+  WriteBase64ToFile(kDexFileDuplicateOffset, temp.GetFile());
+  EXPECT_EQ(temp.GetFile()->Flush(), 0);
+  std::string dexlayout = GetTestAndroidRoot() + "/bin/dexlayout";
+  EXPECT_TRUE(OS::FileExists(dexlayout.c_str())) << dexlayout << " should be a valid file path";
+  std::vector<std::string> dexlayout_exec_argv = {
+      dexlayout,
+      "-a",
+      "-i",
+      "-o",
+      "/dev/null",
+      temp.GetFilename()};
+  std::string error_msg;
+  const bool result = ::art::Exec(dexlayout_exec_argv, &error_msg);
+  EXPECT_TRUE(result);
+  if (!result) {
+    LOG(ERROR) << "Error " << error_msg;
+  }
+}
+
+TEST_F(DexLayoutTest, NullSetRefListElement) {
+  ScratchFile temp;
+  WriteBase64ToFile(kNullSetRefListElementInputDex, temp.GetFile());
+  EXPECT_EQ(temp.GetFile()->Flush(), 0);
+  std::string dexlayout = GetTestAndroidRoot() + "/bin/dexlayout";
+  EXPECT_TRUE(OS::FileExists(dexlayout.c_str())) << dexlayout << " should be a valid file path";
+  std::vector<std::string> dexlayout_exec_argv =
+      { dexlayout, "-o", "/dev/null", temp.GetFilename() };
+  std::string error_msg;
+  const bool result = ::art::Exec(dexlayout_exec_argv, &error_msg);
+  EXPECT_TRUE(result);
+  if (!result) {
+    LOG(ERROR) << "Error " << error_msg;
+  }
+}
+
+TEST_F(DexLayoutTest, UnreferencedCatchHandler) {
+  // Disable test on target.
+  TEST_DISABLED_FOR_TARGET();
+  std::string error_msg;
+  ASSERT_TRUE(UnreferencedCatchHandlerExec(&error_msg)) << error_msg;
+}
+
 }  // namespace art
diff --git a/oatdump/Android.mk b/oatdump/Android.mk
index d80df70..aa07d24 100644
--- a/oatdump/Android.mk
+++ b/oatdump/Android.mk
@@ -41,7 +41,7 @@
 
 .PHONY: dump-oat-core-target-$(TARGET_ARCH)
 ifeq ($(ART_BUILD_TARGET),true)
-dump-oat-core-target-$(TARGET_ARCH): $(TARGET_CORE_IMAGE_default_no-pic_$(ART_PHONY_TEST_TARGET_SUFFIX)) $(OATDUMP)
+dump-oat-core-target-$(TARGET_ARCH): $(TARGET_CORE_IMAGE_default_$(ART_PHONY_TEST_TARGET_SUFFIX)) $(OATDUMP)
 	$(OATDUMP) --image=$(TARGET_CORE_IMG_LOCATION) \
 	  --output=$(ART_DUMP_OAT_PATH)/core.target.$(TARGET_ARCH).oatdump.txt --instruction-set=$(TARGET_ARCH)
 	@echo Output in $(ART_DUMP_OAT_PATH)/core.target.$(TARGET_ARCH).oatdump.txt
@@ -50,7 +50,7 @@
 ifdef TARGET_2ND_ARCH
 .PHONY: dump-oat-core-target-$(TARGET_2ND_ARCH)
 ifeq ($(ART_BUILD_TARGET),true)
-dump-oat-core-target-$(TARGET_2ND_ARCH): $(TARGET_CORE_IMAGE_default_no-pic_$(2ND_ART_PHONY_TEST_TARGET_SUFFIX)) $(OATDUMP)
+dump-oat-core-target-$(TARGET_2ND_ARCH): $(TARGET_CORE_IMAGE_default_$(2ND_ART_PHONY_TEST_TARGET_SUFFIX)) $(OATDUMP)
 	$(OATDUMP) --image=$(TARGET_CORE_IMG_LOCATION) \
 	  --output=$(ART_DUMP_OAT_PATH)/core.target.$(TARGET_2ND_ARCH).oatdump.txt --instruction-set=$(TARGET_2ND_ARCH)
 	@echo Output in $(ART_DUMP_OAT_PATH)/core.target.$(TARGET_2ND_ARCH).oatdump.txt
diff --git a/oatdump/oatdump.cc b/oatdump/oatdump.cc
index a0919a1..becb827 100644
--- a/oatdump/oatdump.cc
+++ b/oatdump/oatdump.cc
@@ -2244,9 +2244,14 @@
           ScopedIndentation indent2(&state->vios_);
           auto* resolved_types = dex_cache->GetResolvedTypes();
           for (size_t i = 0; i < num_types; ++i) {
-            auto* elem = resolved_types[i].Read();
+            auto pair = resolved_types[i].load(std::memory_order_relaxed);
             size_t run = 0;
-            for (size_t j = i + 1; j != num_types && elem == resolved_types[j].Read(); ++j) {
+            for (size_t j = i + 1; j != num_types; ++j) {
+              auto other_pair = resolved_types[j].load(std::memory_order_relaxed);
+              if (pair.index != other_pair.index ||
+                  pair.object.Read() != other_pair.object.Read()) {
+                break;
+              }
               ++run;
             }
             if (run == 0) {
@@ -2256,12 +2261,13 @@
               i = i + run;
             }
             std::string msg;
+            auto* elem = pair.object.Read();
             if (elem == nullptr) {
               msg = "null";
             } else {
               msg = elem->PrettyClass();
             }
-            os << StringPrintf("%p   %s\n", elem, msg.c_str());
+            os << StringPrintf("%p   %u %s\n", elem, pair.index, msg.c_str());
           }
         }
       }
diff --git a/patchoat/patchoat.cc b/patchoat/patchoat.cc
index b9be5f2..18a6670 100644
--- a/patchoat/patchoat.cc
+++ b/patchoat/patchoat.cc
@@ -54,48 +54,6 @@
 
 namespace art {
 
-static bool LocationToFilename(const std::string& location, InstructionSet isa,
-                               std::string* filename) {
-  bool has_system = false;
-  bool has_cache = false;
-  // image_location = /system/framework/boot.art
-  // system_image_filename = /system/framework/<image_isa>/boot.art
-  std::string system_filename(GetSystemImageFilename(location.c_str(), isa));
-  if (OS::FileExists(system_filename.c_str())) {
-    has_system = true;
-  }
-
-  bool have_android_data = false;
-  bool dalvik_cache_exists = false;
-  bool is_global_cache = false;
-  std::string dalvik_cache;
-  GetDalvikCache(GetInstructionSetString(isa), false, &dalvik_cache,
-                 &have_android_data, &dalvik_cache_exists, &is_global_cache);
-
-  std::string cache_filename;
-  if (have_android_data && dalvik_cache_exists) {
-    // Always set output location even if it does not exist,
-    // so that the caller knows where to create the image.
-    //
-    // image_location = /system/framework/boot.art
-    // *image_filename = /data/dalvik-cache/<image_isa>/boot.art
-    std::string error_msg;
-    if (GetDalvikCacheFilename(location.c_str(), dalvik_cache.c_str(),
-                               &cache_filename, &error_msg)) {
-      has_cache = true;
-    }
-  }
-  if (has_system) {
-    *filename = system_filename;
-    return true;
-  } else if (has_cache) {
-    *filename = cache_filename;
-    return true;
-  } else {
-    return false;
-  }
-}
-
 static const OatHeader* GetOatHeader(const ElfFile* elf_file) {
   uint64_t off = 0;
   if (!elf_file->GetSectionOffsetAndSize(".rodata", &off, nullptr)) {
@@ -106,28 +64,10 @@
   return oat_header;
 }
 
-// This function takes an elf file and reads the current patch delta value
-// encoded in its oat header value
-static bool ReadOatPatchDelta(const ElfFile* elf_file, off_t* delta, std::string* error_msg) {
-  const OatHeader* oat_header = GetOatHeader(elf_file);
-  if (oat_header == nullptr) {
-    *error_msg = "Unable to get oat header from elf file.";
-    return false;
-  }
-  if (!oat_header->IsValid()) {
-    *error_msg = "Elf file has an invalid oat header";
-    return false;
-  }
-  *delta = oat_header->GetImagePatchDelta();
-  return true;
-}
-
-static File* CreateOrOpen(const char* name, bool* created) {
+static File* CreateOrOpen(const char* name) {
   if (OS::FileExists(name)) {
-    *created = false;
     return OS::OpenFileReadWrite(name);
   } else {
-    *created = true;
     std::unique_ptr<File> f(OS::CreateEmptyFile(name));
     if (f.get() != nullptr) {
       if (fchmod(f->Fd(), 0644) != 0) {
@@ -206,12 +146,11 @@
   Thread::Current()->TransitionFromRunnableToSuspended(kNative);
   ScopedObjectAccess soa(Thread::Current());
 
-  t.NewTiming("Image and oat Patching setup");
+  t.NewTiming("Image Patching setup");
   std::vector<gc::space::ImageSpace*> spaces = Runtime::Current()->GetHeap()->GetBootImageSpaces();
   std::map<gc::space::ImageSpace*, std::unique_ptr<File>> space_to_file_map;
   std::map<gc::space::ImageSpace*, std::unique_ptr<MemMap>> space_to_memmap_map;
   std::map<gc::space::ImageSpace*, PatchOat> space_to_patchoat_map;
-  std::map<gc::space::ImageSpace*, bool> space_to_skip_patching_map;
 
   for (size_t i = 0; i < spaces.size(); ++i) {
     gc::space::ImageSpace* space = spaces[i];
@@ -255,8 +194,7 @@
     space_to_memmap_map.emplace(space, std::move(image));
   }
 
-  // Do a first pass over the image spaces. Symlink PIC oat and vdex files, and
-  // prepare PatchOat instances for the rest.
+  // Symlink PIC oat and vdex files and patch the image spaces in memory.
   for (size_t i = 0; i < spaces.size(); ++i) {
     gc::space::ImageSpace* space = spaces[i];
     std::string input_image_filename = space->GetImageFilename();
@@ -277,14 +215,17 @@
       return false;
     }
 
-    bool skip_patching_oat = false;
     MaybePic is_oat_pic = IsOatPic(elf.get());
     if (is_oat_pic >= ERROR_FIRST) {
       // Error logged by IsOatPic
       return false;
-    } else if (is_oat_pic == PIC) {
-      // Do not need to do ELF-file patching. Create a symlink and skip the ELF patching.
+    } else if (is_oat_pic == NOT_PIC) {
+      LOG(ERROR) << "patchoat cannot be used on non-PIC oat file: " << input_oat_file->GetPath();
+      return false;
+    } else {
+      CHECK(is_oat_pic == PIC);
 
+      // Create a symlink.
       std::string converted_image_filename = space->GetImageLocation();
       std::replace(converted_image_filename.begin() + 1, converted_image_filename.end(), '/', '@');
       std::string output_image_filename = output_directory +
@@ -296,23 +237,16 @@
           ImageHeader::GetOatLocationFromImageLocation(output_image_filename);
 
       if (!ReplaceOatFileWithSymlink(input_oat_file->GetPath(),
-                                     output_oat_filename,
-                                     false,
-                                     true) ||
+                                     output_oat_filename) ||
           !SymlinkFile(input_vdex_filename, output_vdex_filename)) {
         // Errors already logged by above call.
         return false;
       }
-      // Don't patch the OAT, since we just symlinked it. Image still needs patching.
-      skip_patching_oat = true;
-    } else {
-      CHECK(is_oat_pic == NOT_PIC);
     }
 
     PatchOat& p = space_to_patchoat_map.emplace(space,
                                                 PatchOat(
                                                     isa,
-                                                    elf.release(),
                                                     space_to_memmap_map.find(space)->second.get(),
                                                     space->GetLiveBitmap(),
                                                     space->GetMemMap(),
@@ -320,36 +254,24 @@
                                                     &space_to_memmap_map,
                                                     timings)).first->second;
 
-    t.NewTiming("Patching files");
-    if (!skip_patching_oat && !p.PatchElf()) {
-      LOG(ERROR) << "Failed to patch oat file " << input_oat_file->GetPath();
-      return false;
-    }
+    t.NewTiming("Patching image");
     if (!p.PatchImage(i == 0)) {
       LOG(ERROR) << "Failed to patch image file " << input_image_filename;
       return false;
     }
-
-    space_to_skip_patching_map.emplace(space, skip_patching_oat);
   }
 
-  // Do a second pass over the image spaces. Patch image files, non-PIC oat files
-  // and symlink their corresponding vdex files.
+  // Write the patched image spaces.
   for (size_t i = 0; i < spaces.size(); ++i) {
     gc::space::ImageSpace* space = spaces[i];
-    std::string input_image_filename = space->GetImageFilename();
-    std::string input_vdex_filename =
-        ImageHeader::GetVdexLocationFromImageLocation(input_image_filename);
 
-    t.NewTiming("Writing files");
+    t.NewTiming("Writing image");
     std::string converted_image_filename = space->GetImageLocation();
     std::replace(converted_image_filename.begin() + 1, converted_image_filename.end(), '/', '@');
     std::string output_image_filename = output_directory +
         (android::base::StartsWith(converted_image_filename, "/") ? "" : "/") +
         converted_image_filename;
-    bool new_oat_out;
-    std::unique_ptr<File>
-        output_image_file(CreateOrOpen(output_image_filename.c_str(), &new_oat_out));
+    std::unique_ptr<File> output_image_file(CreateOrOpen(output_image_filename.c_str()));
     if (output_image_file.get() == nullptr) {
       LOG(ERROR) << "Failed to open output image file at " << output_image_filename;
       return false;
@@ -362,48 +284,10 @@
     if (!success) {
       return false;
     }
-
-    bool skip_patching_oat = space_to_skip_patching_map.find(space)->second;
-    if (!skip_patching_oat) {
-      std::string output_vdex_filename =
-          ImageHeader::GetVdexLocationFromImageLocation(output_image_filename);
-      std::string output_oat_filename =
-          ImageHeader::GetOatLocationFromImageLocation(output_image_filename);
-
-      std::unique_ptr<File>
-          output_oat_file(CreateOrOpen(output_oat_filename.c_str(), &new_oat_out));
-      if (output_oat_file.get() == nullptr) {
-        LOG(ERROR) << "Failed to open output oat file at " << output_oat_filename;
-        return false;
-      }
-      success = p.WriteElf(output_oat_file.get());
-      success = FinishFile(output_oat_file.get(), success);
-      if (success) {
-        success = SymlinkFile(input_vdex_filename, output_vdex_filename);
-      }
-      if (!success) {
-        return false;
-      }
-    }
   }
   return true;
 }
 
-bool PatchOat::WriteElf(File* out) {
-  TimingLogger::ScopedTiming t("Writing Elf File", timings_);
-
-  CHECK(oat_file_.get() != nullptr);
-  CHECK(out != nullptr);
-  size_t expect = oat_file_->Size();
-  if (out->WriteFully(reinterpret_cast<char*>(oat_file_->Begin()), expect) &&
-      out->SetLength(expect) == 0) {
-    return true;
-  } else {
-    LOG(ERROR) << "Writing to oat file " << out->GetPath() << " failed.";
-    return false;
-  }
-}
-
 bool PatchOat::WriteImage(File* out) {
   TimingLogger::ScopedTiming t("Writing image File", timings_);
   std::string error_msg;
@@ -466,22 +350,7 @@
 }
 
 bool PatchOat::ReplaceOatFileWithSymlink(const std::string& input_oat_filename,
-                                         const std::string& output_oat_filename,
-                                         bool output_oat_opened_from_fd,
-                                         bool new_oat_out) {
-  // Need a file when we are PIC, since we symlink over it. Refusing to symlink into FD.
-  if (output_oat_opened_from_fd) {
-    // TODO: installd uses --output-oat-fd. Should we change class linking logic for PIC?
-    LOG(ERROR) << "No output oat filename specified, needs filename for when we are PIC";
-    return false;
-  }
-
-  // Image was PIC. Create symlink where the oat is supposed to go.
-  if (!new_oat_out) {
-    LOG(ERROR) << "Oat file " << output_oat_filename << " already exists, refusing to overwrite";
-    return false;
-  }
-
+                                         const std::string& output_oat_filename) {
   // Delete the original file, since we won't need it.
   unlink(output_oat_filename.c_str());
 
@@ -643,8 +512,8 @@
     if (orig_strings != nullptr) {
       orig_dex_cache->FixupStrings(RelocatedCopyOf(orig_strings), RelocatedPointerVisitor(this));
     }
-    GcRoot<mirror::Class>* orig_types = orig_dex_cache->GetResolvedTypes();
-    GcRoot<mirror::Class>* relocated_types = RelocatedAddressOfPointer(orig_types);
+    mirror::TypeDexCacheType* orig_types = orig_dex_cache->GetResolvedTypes();
+    mirror::TypeDexCacheType* relocated_types = RelocatedAddressOfPointer(orig_types);
     copy_dex_cache->SetField64<false>(
         mirror::DexCache::ResolvedTypesOffset(),
         static_cast<int64_t>(reinterpret_cast<uintptr_t>(relocated_types)));
@@ -807,133 +676,6 @@
       object->GetDataPtrSize(pointer_size)), pointer_size);
 }
 
-bool PatchOat::Patch(File* input_oat, off_t delta, File* output_oat, TimingLogger* timings,
-                     bool output_oat_opened_from_fd, bool new_oat_out) {
-  CHECK(input_oat != nullptr);
-  CHECK(output_oat != nullptr);
-  CHECK_GE(input_oat->Fd(), 0);
-  CHECK_GE(output_oat->Fd(), 0);
-  TimingLogger::ScopedTiming t("Setup Oat File Patching", timings);
-
-  std::string error_msg;
-  std::unique_ptr<ElfFile> elf(ElfFile::Open(input_oat,
-                                             PROT_READ | PROT_WRITE, MAP_PRIVATE, &error_msg));
-  if (elf.get() == nullptr) {
-    LOG(ERROR) << "unable to open oat file " << input_oat->GetPath() << " : " << error_msg;
-    return false;
-  }
-
-  MaybePic is_oat_pic = IsOatPic(elf.get());
-  if (is_oat_pic >= ERROR_FIRST) {
-    // Error logged by IsOatPic
-    return false;
-  } else if (is_oat_pic == PIC) {
-    // Do not need to do ELF-file patching. Create a symlink and skip the rest.
-    // Any errors will be logged by the function call.
-    return ReplaceOatFileWithSymlink(input_oat->GetPath(),
-                                     output_oat->GetPath(),
-                                     output_oat_opened_from_fd,
-                                     new_oat_out);
-  } else {
-    CHECK(is_oat_pic == NOT_PIC);
-  }
-
-  PatchOat p(elf.release(), delta, timings);
-  t.NewTiming("Patch Oat file");
-  if (!p.PatchElf()) {
-    return false;
-  }
-
-  t.NewTiming("Writing oat file");
-  if (!p.WriteElf(output_oat)) {
-    return false;
-  }
-  return true;
-}
-
-template <typename ElfFileImpl>
-bool PatchOat::PatchOatHeader(ElfFileImpl* oat_file) {
-  auto rodata_sec = oat_file->FindSectionByName(".rodata");
-  if (rodata_sec == nullptr) {
-    return false;
-  }
-  OatHeader* oat_header = reinterpret_cast<OatHeader*>(oat_file->Begin() + rodata_sec->sh_offset);
-  if (!oat_header->IsValid()) {
-    LOG(ERROR) << "Elf file " << oat_file->GetFilePath() << " has an invalid oat header";
-    return false;
-  }
-  oat_header->RelocateOat(delta_);
-  return true;
-}
-
-bool PatchOat::PatchElf() {
-  if (oat_file_->Is64Bit()) {
-    return PatchElf<ElfFileImpl64>(oat_file_->GetImpl64());
-  } else {
-    return PatchElf<ElfFileImpl32>(oat_file_->GetImpl32());
-  }
-}
-
-template <typename ElfFileImpl>
-bool PatchOat::PatchElf(ElfFileImpl* oat_file) {
-  TimingLogger::ScopedTiming t("Fixup Elf Text Section", timings_);
-
-  // Fix up absolute references to locations within the boot image.
-  if (!oat_file->ApplyOatPatchesTo(".text", delta_)) {
-    return false;
-  }
-
-  // Update the OatHeader fields referencing the boot image.
-  if (!PatchOatHeader<ElfFileImpl>(oat_file)) {
-    return false;
-  }
-
-  bool need_boot_oat_fixup = true;
-  for (unsigned int i = 0; i < oat_file->GetProgramHeaderNum(); ++i) {
-    auto hdr = oat_file->GetProgramHeader(i);
-    if (hdr->p_type == PT_LOAD && hdr->p_vaddr == 0u) {
-      need_boot_oat_fixup = false;
-      break;
-    }
-  }
-  if (!need_boot_oat_fixup) {
-    // This is an app oat file that can be loaded at an arbitrary address in memory.
-    // Boot image references were patched above and there's nothing else to do.
-    return true;
-  }
-
-  // This is a boot oat file that's loaded at a particular address and we need
-  // to patch all absolute addresses, starting with ELF program headers.
-
-  t.NewTiming("Fixup Elf Headers");
-  // Fixup Phdr's
-  oat_file->FixupProgramHeaders(delta_);
-
-  t.NewTiming("Fixup Section Headers");
-  // Fixup Shdr's
-  oat_file->FixupSectionHeaders(delta_);
-
-  t.NewTiming("Fixup Dynamics");
-  oat_file->FixupDynamic(delta_);
-
-  t.NewTiming("Fixup Elf Symbols");
-  // Fixup dynsym
-  if (!oat_file->FixupSymbols(delta_, true)) {
-    return false;
-  }
-  // Fixup symtab
-  if (!oat_file->FixupSymbols(delta_, false)) {
-    return false;
-  }
-
-  t.NewTiming("Fixup Debug Sections");
-  if (!oat_file->FixupDebugSections(delta_)) {
-    return false;
-  }
-
-  return true;
-}
-
 static int orig_argc;
 static char** orig_argv;
 
@@ -968,32 +710,10 @@
   UsageError("Usage: patchoat [options]...");
   UsageError("");
   UsageError("  --instruction-set=<isa>: Specifies the instruction set the patched code is");
-  UsageError("      compiled for. Required if you use --input-oat-location");
-  UsageError("");
-  UsageError("  --input-oat-file=<file.oat>: Specifies the exact filename of the oat file to be");
-  UsageError("      patched.");
-  UsageError("");
-  UsageError("  --input-oat-fd=<file-descriptor>: Specifies the file-descriptor of the oat file");
-  UsageError("      to be patched.");
-  UsageError("");
-  UsageError("  --input-vdex-fd=<file-descriptor>: Specifies the file-descriptor of the vdex file");
-  UsageError("      associated with the oat file.");
-  UsageError("");
-  UsageError("  --input-oat-location=<file.oat>: Specifies the 'location' to read the patched");
-  UsageError("      oat file from. If used one must also supply the --instruction-set");
+  UsageError("      compiled for (required).");
   UsageError("");
   UsageError("  --input-image-location=<file.art>: Specifies the 'location' of the image file to");
-  UsageError("      be patched. If --instruction-set is not given it will use the instruction set");
-  UsageError("      extracted from the --input-oat-file.");
-  UsageError("");
-  UsageError("  --output-oat-file=<file.oat>: Specifies the exact file to write the patched oat");
-  UsageError("      file to.");
-  UsageError("");
-  UsageError("  --output-oat-fd=<file-descriptor>: Specifies the file-descriptor to write the");
-  UsageError("      patched oat file to.");
-  UsageError("");
-  UsageError("  --output-vdex-fd=<file-descriptor>: Specifies the file-descriptor to copy the");
-  UsageError("      the vdex file associated with the patch oat file to.");
+  UsageError("      be patched.");
   UsageError("");
   UsageError("  --output-image-file=<file.art>: Specifies the exact file to write the patched");
   UsageError("      image file to.");
@@ -1001,15 +721,6 @@
   UsageError("  --base-offset-delta=<delta>: Specify the amount to change the old base-offset by.");
   UsageError("      This value may be negative.");
   UsageError("");
-  UsageError("  --patched-image-location=<file.art>: Relocate the oat file to be the same as the");
-  UsageError("      image at the given location. If used one must also specify the");
-  UsageError("      --instruction-set flag. It will search for this image in the same way that");
-  UsageError("      is done when loading one.");
-  UsageError("");
-  UsageError("  --lock-output: Obtain a flock on output oat file before starting.");
-  UsageError("");
-  UsageError("  --no-lock-output: Do not attempt to obtain a flock on output oat file.");
-  UsageError("");
   UsageError("  --dump-timings: dump out patch timing information");
   UsageError("");
   UsageError("  --no-dump-timings: do not dump out patch timing information");
@@ -1018,34 +729,6 @@
   exit(EXIT_FAILURE);
 }
 
-static bool ReadBaseDelta(const char* name, off_t* delta, std::string* error_msg) {
-  CHECK(name != nullptr);
-  CHECK(delta != nullptr);
-  std::unique_ptr<File> file;
-  if (OS::FileExists(name)) {
-    file.reset(OS::OpenFileForReading(name));
-    if (file.get() == nullptr) {
-      *error_msg = "Failed to open file %s for reading";
-      return false;
-    }
-  } else {
-    *error_msg = "File %s does not exist";
-    return false;
-  }
-  CHECK(file.get() != nullptr);
-  ImageHeader hdr;
-  if (sizeof(hdr) != file->Read(reinterpret_cast<char*>(&hdr), sizeof(hdr), 0)) {
-    *error_msg = "Failed to read file %s";
-    return false;
-  }
-  if (!hdr.IsValid()) {
-    *error_msg = "%s does not contain a valid image header.";
-    return false;
-  }
-  *delta = hdr.GetPatchDelta();
-  return true;
-}
-
 static int patchoat_image(TimingLogger& timings,
                           InstructionSet isa,
                           const std::string& input_image_location,
@@ -1084,293 +767,6 @@
   return ret ? EXIT_SUCCESS : EXIT_FAILURE;
 }
 
-static int patchoat_oat(TimingLogger& timings,
-                        InstructionSet isa,
-                        const std::string& patched_image_location,
-                        off_t base_delta,
-                        bool base_delta_set,
-                        int input_oat_fd,
-                        int input_vdex_fd,
-                        const std::string& input_oat_location,
-                        std::string input_oat_filename,
-                        bool have_input_oat,
-                        int output_oat_fd,
-                        int output_vdex_fd,
-                        std::string output_oat_filename,
-                        bool have_output_oat,
-                        bool lock_output,
-                        bool debug) {
-  {
-    // Only 1 of these may be set.
-    uint32_t cnt = 0;
-    cnt += (base_delta_set) ? 1 : 0;
-    cnt += (!patched_image_location.empty()) ? 1 : 0;
-    if (cnt > 1) {
-      Usage("Only one of --base-offset-delta or --patched-image-location may be used.");
-    } else if (cnt == 0) {
-      Usage("Must specify --base-offset-delta or --patched-image-location.");
-    }
-  }
-
-  if (!have_input_oat || !have_output_oat) {
-    Usage("Both input and output oat must be supplied to patch an app odex.");
-  }
-
-  if (!input_oat_location.empty()) {
-    if (!LocationToFilename(input_oat_location, isa, &input_oat_filename)) {
-      Usage("Unable to find filename for input oat location %s", input_oat_location.c_str());
-    }
-    if (debug) {
-      LOG(INFO) << "Using input-oat-file " << input_oat_filename;
-    }
-  }
-
-  if ((input_oat_fd == -1) != (input_vdex_fd == -1)) {
-    Usage("Either both input oat and vdex have to be passed as file descriptors or none of them");
-  } else if ((output_oat_fd == -1) != (output_vdex_fd == -1)) {
-    Usage("Either both output oat and vdex have to be passed as file descriptors or none of them");
-  }
-
-  bool match_delta = false;
-  if (!patched_image_location.empty()) {
-    std::string system_filename;
-    bool has_system = false;
-    std::string cache_filename;
-    bool has_cache = false;
-    bool has_android_data_unused = false;
-    bool is_global_cache = false;
-    if (!gc::space::ImageSpace::FindImageFilename(patched_image_location.c_str(), isa,
-                                                  &system_filename, &has_system, &cache_filename,
-                                                  &has_android_data_unused, &has_cache,
-                                                  &is_global_cache)) {
-      Usage("Unable to determine image file for location %s", patched_image_location.c_str());
-    }
-    std::string patched_image_filename;
-    if (has_cache) {
-      patched_image_filename = cache_filename;
-    } else if (has_system) {
-      LOG(WARNING) << "Only image file found was in /system for image location "
-          << patched_image_location;
-      patched_image_filename = system_filename;
-    } else {
-      Usage("Unable to determine image file for location %s", patched_image_location.c_str());
-    }
-    if (debug) {
-      LOG(INFO) << "Using patched-image-file " << patched_image_filename;
-    }
-
-    base_delta_set = true;
-    match_delta = true;
-    std::string error_msg;
-    if (!ReadBaseDelta(patched_image_filename.c_str(), &base_delta, &error_msg)) {
-      Usage(error_msg.c_str(), patched_image_filename.c_str());
-    }
-  }
-
-  if (!IsAligned<kPageSize>(base_delta)) {
-    Usage("Base offset/delta must be alligned to a pagesize (0x%08x) boundary.", kPageSize);
-  }
-
-  // We can symlink VDEX only if we have both input and output specified as filenames.
-  // Store that piece of information before we possibly create bogus filenames for
-  // files passed as file descriptors.
-  bool symlink_vdex = !input_oat_filename.empty() && !output_oat_filename.empty();
-
-  // Infer names of VDEX files.
-  std::string input_vdex_filename;
-  std::string output_vdex_filename;
-  if (!input_oat_filename.empty()) {
-    input_vdex_filename = ReplaceFileExtension(input_oat_filename, "vdex");
-  }
-  if (!output_oat_filename.empty()) {
-    output_vdex_filename = ReplaceFileExtension(output_oat_filename, "vdex");
-  }
-
-  // Do we need to cleanup output files if we fail?
-  bool new_oat_out = false;
-  bool new_vdex_out = false;
-
-  std::unique_ptr<File> input_oat;
-  std::unique_ptr<File> output_oat;
-
-  if (input_oat_fd != -1) {
-    if (input_oat_filename.empty()) {
-      input_oat_filename = "input-oat-file";
-    }
-    input_oat.reset(new File(input_oat_fd, input_oat_filename, false));
-    if (input_oat_fd == output_oat_fd) {
-      input_oat.get()->DisableAutoClose();
-    }
-    if (input_oat == nullptr) {
-      // Unlikely, but ensure exhaustive logging in non-0 exit code case
-      LOG(ERROR) << "Failed to open input oat file by its FD" << input_oat_fd;
-      return EXIT_FAILURE;
-    }
-  } else {
-    CHECK(!input_oat_filename.empty());
-    input_oat.reset(OS::OpenFileForReading(input_oat_filename.c_str()));
-    if (input_oat == nullptr) {
-      int err = errno;
-      LOG(ERROR) << "Failed to open input oat file " << input_oat_filename
-          << ": " << strerror(err) << "(" << err << ")";
-      return EXIT_FAILURE;
-    }
-  }
-
-  std::string error_msg;
-  std::unique_ptr<ElfFile> elf(ElfFile::Open(input_oat.get(), PROT_READ, MAP_PRIVATE, &error_msg));
-  if (elf.get() == nullptr) {
-    LOG(ERROR) << "unable to open oat file " << input_oat->GetPath() << " : " << error_msg;
-    return EXIT_FAILURE;
-  }
-  if (!elf->HasSection(".text.oat_patches")) {
-    LOG(ERROR) << "missing oat patch section in input oat file " << input_oat->GetPath();
-    return EXIT_FAILURE;
-  }
-
-  if (output_oat_fd != -1) {
-    if (output_oat_filename.empty()) {
-      output_oat_filename = "output-oat-file";
-    }
-    output_oat.reset(new File(output_oat_fd, output_oat_filename, true));
-    if (output_oat == nullptr) {
-      // Unlikely, but ensure exhaustive logging in non-0 exit code case
-      LOG(ERROR) << "Failed to open output oat file by its FD" << output_oat_fd;
-    }
-  } else {
-    CHECK(!output_oat_filename.empty());
-    output_oat.reset(CreateOrOpen(output_oat_filename.c_str(), &new_oat_out));
-    if (output_oat == nullptr) {
-      int err = errno;
-      LOG(ERROR) << "Failed to open output oat file " << output_oat_filename
-          << ": " << strerror(err) << "(" << err << ")";
-    }
-  }
-
-  // Open VDEX files if we are not symlinking them.
-  std::unique_ptr<File> input_vdex;
-  std::unique_ptr<File> output_vdex;
-  if (symlink_vdex) {
-    new_vdex_out = !OS::FileExists(output_vdex_filename.c_str());
-  } else {
-    if (input_vdex_fd != -1) {
-      input_vdex.reset(new File(input_vdex_fd, input_vdex_filename, true));
-      if (input_vdex == nullptr) {
-        // Unlikely, but ensure exhaustive logging in non-0 exit code case
-        LOG(ERROR) << "Failed to open input vdex file by its FD" << input_vdex_fd;
-      }
-    } else {
-      input_vdex.reset(OS::OpenFileForReading(input_vdex_filename.c_str()));
-      if (input_vdex == nullptr) {
-        PLOG(ERROR) << "Failed to open input vdex file " << input_vdex_filename;
-        return EXIT_FAILURE;
-      }
-    }
-    if (output_vdex_fd != -1) {
-      output_vdex.reset(new File(output_vdex_fd, output_vdex_filename, true));
-      if (output_vdex == nullptr) {
-        // Unlikely, but ensure exhaustive logging in non-0 exit code case
-        LOG(ERROR) << "Failed to open output vdex file by its FD" << output_vdex_fd;
-      }
-    } else {
-      output_vdex.reset(CreateOrOpen(output_vdex_filename.c_str(), &new_vdex_out));
-      if (output_vdex == nullptr) {
-        PLOG(ERROR) << "Failed to open output vdex file " << output_vdex_filename;
-        return EXIT_FAILURE;
-      }
-    }
-  }
-
-  // TODO: get rid of this.
-  auto cleanup = [&output_oat_filename, &output_vdex_filename, &new_oat_out, &new_vdex_out]
-                 (bool success) {
-    if (!success) {
-      if (new_oat_out) {
-        CHECK(!output_oat_filename.empty());
-        unlink(output_oat_filename.c_str());
-      }
-      if (new_vdex_out) {
-        CHECK(!output_vdex_filename.empty());
-        unlink(output_vdex_filename.c_str());
-      }
-    }
-
-    if (kIsDebugBuild) {
-      LOG(INFO) << "Cleaning up.. success? " << success;
-    }
-  };
-
-  if (output_oat.get() == nullptr) {
-    cleanup(false);
-    return EXIT_FAILURE;
-  }
-
-  if (match_delta) {
-    // Figure out what the current delta is so we can match it to the desired delta.
-    off_t current_delta = 0;
-    if (!ReadOatPatchDelta(elf.get(), &current_delta, &error_msg)) {
-      LOG(ERROR) << "Unable to get current delta: " << error_msg;
-      cleanup(false);
-      return EXIT_FAILURE;
-    }
-    // Before this line base_delta is the desired final delta. We need it to be the actual amount to
-    // change everything by. We subtract the current delta from it to make it this.
-    base_delta -= current_delta;
-    if (!IsAligned<kPageSize>(base_delta)) {
-      LOG(ERROR) << "Given image file was relocated by an illegal delta";
-      cleanup(false);
-      return false;
-    }
-  }
-
-  if (debug) {
-    LOG(INFO) << "moving offset by " << base_delta
-        << " (0x" << std::hex << base_delta << ") bytes or "
-        << std::dec << (base_delta/kPageSize) << " pages.";
-  }
-
-  ScopedFlock output_oat_lock;
-  if (lock_output) {
-    if (!output_oat_lock.Init(output_oat.get(), &error_msg)) {
-      LOG(ERROR) << "Unable to lock output oat " << output_oat->GetPath() << ": " << error_msg;
-      cleanup(false);
-      return EXIT_FAILURE;
-    }
-  }
-
-  TimingLogger::ScopedTiming pt("patch oat", &timings);
-  bool ret = PatchOat::Patch(input_oat.get(), base_delta, output_oat.get(), &timings,
-                             output_oat_fd >= 0,  // was it opened from FD?
-                             new_oat_out);
-  ret = FinishFile(output_oat.get(), ret);
-
-  if (ret) {
-    if (symlink_vdex) {
-      ret = SymlinkFile(input_vdex_filename, output_vdex_filename);
-    } else {
-      ret = unix_file::CopyFile(*input_vdex.get(), output_vdex.get());
-    }
-  }
-
-  if (kIsDebugBuild) {
-    LOG(INFO) << "Exiting with return ... " << ret;
-  }
-  cleanup(ret);
-  return ret ? EXIT_SUCCESS : EXIT_FAILURE;
-}
-
-static int ParseFd(const StringPiece& option, const char* cmdline_arg) {
-  int fd;
-  const char* fd_str = option.substr(strlen(cmdline_arg)).data();
-  if (!ParseInt(fd_str, &fd)) {
-    Usage("Failed to parse %d argument '%s' as an integer", cmdline_arg, fd_str);
-  }
-  if (fd < 0) {
-    Usage("%s pass a negative value %d", cmdline_arg, fd);
-  }
-  return fd;
-}
-
 static int patchoat(int argc, char **argv) {
   InitLogging(argv, Runtime::Aborter);
   MemMap::Init();
@@ -1392,23 +788,11 @@
   // cmd line args
   bool isa_set = false;
   InstructionSet isa = kNone;
-  std::string input_oat_filename;
-  std::string input_oat_location;
-  int input_oat_fd = -1;
-  int input_vdex_fd = -1;
-  bool have_input_oat = false;
   std::string input_image_location;
-  std::string output_oat_filename;
-  int output_oat_fd = -1;
-  int output_vdex_fd = -1;
-  bool have_output_oat = false;
   std::string output_image_filename;
   off_t base_delta = 0;
   bool base_delta_set = false;
-  std::string patched_image_filename;
-  std::string patched_image_location;
   bool dump_timings = kIsDebugBuild;
-  bool lock_output = true;
 
   for (int i = 0; i < argc; ++i) {
     const StringPiece option(argv[i]);
@@ -1423,42 +807,8 @@
       if (isa == kNone) {
         Usage("Unknown or invalid instruction set %s", isa_str);
       }
-    } else if (option.starts_with("--input-oat-location=")) {
-      if (have_input_oat) {
-        Usage("Only one of --input-oat-file, --input-oat-location and --input-oat-fd may be used.");
-      }
-      have_input_oat = true;
-      input_oat_location = option.substr(strlen("--input-oat-location=")).data();
-    } else if (option.starts_with("--input-oat-file=")) {
-      if (have_input_oat) {
-        Usage("Only one of --input-oat-file, --input-oat-location and --input-oat-fd may be used.");
-      }
-      have_input_oat = true;
-      input_oat_filename = option.substr(strlen("--input-oat-file=")).data();
-    } else if (option.starts_with("--input-oat-fd=")) {
-      if (have_input_oat) {
-        Usage("Only one of --input-oat-file, --input-oat-location and --input-oat-fd may be used.");
-      }
-      have_input_oat = true;
-      input_oat_fd = ParseFd(option, "--input-oat-fd=");
-    } else if (option.starts_with("--input-vdex-fd=")) {
-      input_vdex_fd = ParseFd(option, "--input-vdex-fd=");
     } else if (option.starts_with("--input-image-location=")) {
       input_image_location = option.substr(strlen("--input-image-location=")).data();
-    } else if (option.starts_with("--output-oat-file=")) {
-      if (have_output_oat) {
-        Usage("Only one of --output-oat-file, and --output-oat-fd may be used.");
-      }
-      have_output_oat = true;
-      output_oat_filename = option.substr(strlen("--output-oat-file=")).data();
-    } else if (option.starts_with("--output-oat-fd=")) {
-      if (have_output_oat) {
-        Usage("Only one of --output-oat-file, --output-oat-fd may be used.");
-      }
-      have_output_oat = true;
-      output_oat_fd = ParseFd(option, "--output-oat-fd=");
-    } else if (option.starts_with("--output-vdex-fd=")) {
-      output_vdex_fd = ParseFd(option, "--output-vdex-fd=");
     } else if (option.starts_with("--output-image-file=")) {
       output_image_filename = option.substr(strlen("--output-image-file=")).data();
     } else if (option.starts_with("--base-offset-delta=")) {
@@ -1467,12 +817,6 @@
       if (!ParseInt(base_delta_str, &base_delta)) {
         Usage("Failed to parse --base-offset-delta argument '%s' as an off_t", base_delta_str);
       }
-    } else if (option.starts_with("--patched-image-location=")) {
-      patched_image_location = option.substr(strlen("--patched-image-location=")).data();
-    } else if (option == "--lock-output") {
-      lock_output = true;
-    } else if (option == "--no-lock-output") {
-      lock_output = false;
     } else if (option == "--dump-timings") {
       dump_timings = true;
     } else if (option == "--no-dump-timings") {
@@ -1487,33 +831,13 @@
     Usage("Instruction set must be set.");
   }
 
-  int ret;
-  if (!input_image_location.empty()) {
-    ret = patchoat_image(timings,
-                         isa,
-                         input_image_location,
-                         output_image_filename,
-                         base_delta,
-                         base_delta_set,
-                         debug);
-  } else {
-    ret = patchoat_oat(timings,
-                       isa,
-                       patched_image_location,
-                       base_delta,
-                       base_delta_set,
-                       input_oat_fd,
-                       input_vdex_fd,
-                       input_oat_location,
-                       input_oat_filename,
-                       have_input_oat,
-                       output_oat_fd,
-                       output_vdex_fd,
-                       output_oat_filename,
-                       have_output_oat,
-                       lock_output,
-                       debug);
-  }
+  int ret = patchoat_image(timings,
+                           isa,
+                           input_image_location,
+                           output_image_filename,
+                           base_delta,
+                           base_delta_set,
+                           debug);
 
   timings.EndTiming();
   if (dump_timings) {
diff --git a/patchoat/patchoat.h b/patchoat/patchoat.h
index a519631..e15a6bc 100644
--- a/patchoat/patchoat.h
+++ b/patchoat/patchoat.h
@@ -44,17 +44,7 @@
 
 class PatchOat {
  public:
-  // Patch only the oat file
-  static bool Patch(File* oat_in, off_t delta, File* oat_out, TimingLogger* timings,
-                    bool output_oat_opened_from_fd,  // Was this using --oatput-oat-fd ?
-                    bool new_oat_out);               // Output oat was a new file created by us?
-
-  // Patch only the image (art file)
-  static bool Patch(const std::string& art_location, off_t delta, File* art_out, InstructionSet isa,
-                    TimingLogger* timings);
-
-  // Patch both the image and the oat file
-  static bool Patch(const std::string& art_location,
+  static bool Patch(const std::string& image_location,
                     off_t delta,
                     const std::string& output_directory,
                     InstructionSet isa,
@@ -64,18 +54,11 @@
   PatchOat(PatchOat&&) = default;
 
  private:
-  // Takes ownership only of the ElfFile. All other pointers are only borrowed.
-  PatchOat(ElfFile* oat_file, off_t delta, TimingLogger* timings)
-      : oat_file_(oat_file), image_(nullptr), bitmap_(nullptr), heap_(nullptr), delta_(delta),
-        isa_(kNone), space_map_(nullptr), timings_(timings) {}
-  PatchOat(InstructionSet isa, MemMap* image, gc::accounting::ContinuousSpaceBitmap* bitmap,
-           MemMap* heap, off_t delta, TimingLogger* timings)
-      : image_(image), bitmap_(bitmap), heap_(heap),
-        delta_(delta), isa_(isa), space_map_(nullptr), timings_(timings) {}
-  PatchOat(InstructionSet isa, ElfFile* oat_file, MemMap* image,
+  // All pointers are only borrowed.
+  PatchOat(InstructionSet isa, MemMap* image,
            gc::accounting::ContinuousSpaceBitmap* bitmap, MemMap* heap, off_t delta,
            std::map<gc::space::ImageSpace*, std::unique_ptr<MemMap>>* map, TimingLogger* timings)
-      : oat_file_(oat_file), image_(image), bitmap_(bitmap), heap_(heap),
+      : image_(image), bitmap_(bitmap), heap_(heap),
         delta_(delta), isa_(isa), space_map_(map), timings_(timings) {}
 
   // Was the .art image at image_path made with --compile-pic ?
@@ -94,9 +77,7 @@
   // Attempt to replace the file with a symlink
   // Returns false if it fails
   static bool ReplaceOatFileWithSymlink(const std::string& input_oat_filename,
-                                        const std::string& output_oat_filename,
-                                        bool output_oat_opened_from_fd,
-                                        bool new_oat_out);  // Output oat was newly created?
+                                        const std::string& output_oat_filename);
 
   static void BitmapCallback(mirror::Object* obj, void* arg)
       REQUIRES_SHARED(Locks::mutator_lock_) {
@@ -108,13 +89,6 @@
   void FixupMethod(ArtMethod* object, ArtMethod* copy)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
-  // Patches oat in place, modifying the oat_file given to the constructor.
-  bool PatchElf();
-  template <typename ElfFileImpl>
-  bool PatchElf(ElfFileImpl* oat_file);
-  template <typename ElfFileImpl>
-  bool PatchOatHeader(ElfFileImpl* oat_file);
-
   bool PatchImage(bool primary_image) REQUIRES_SHARED(Locks::mutator_lock_);
   void PatchArtFields(const ImageHeader* image_header) REQUIRES_SHARED(Locks::mutator_lock_);
   void PatchArtMethods(const ImageHeader* image_header) REQUIRES_SHARED(Locks::mutator_lock_);
@@ -128,7 +102,6 @@
   void PatchDexFileArrays(mirror::ObjectArray<mirror::Object>* img_roots)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
-  bool WriteElf(File* out);
   bool WriteImage(File* out);
 
   template <typename T>
@@ -175,19 +148,6 @@
     return reinterpret_cast<T*>(ret);
   }
 
-  template <typename T>
-  T RelocatedAddressOfIntPointer(T obj) const {
-    if (obj == 0) {
-      return obj;
-    }
-    T ret = obj + delta_;
-    // Trim off high bits in case negative relocation with 64 bit patchoat.
-    if (Is32BitISA()) {
-      ret = static_cast<T>(static_cast<uint32_t>(ret));
-    }
-    return ret;
-  }
-
   bool Is32BitISA() const {
     return InstructionSetPointerSize(isa_) == PointerSize::k32;
   }
@@ -213,8 +173,6 @@
     mirror::Object* const copy_;
   };
 
-  // The elf file we are patching.
-  std::unique_ptr<ElfFile> oat_file_;
   // A mmap of the image we are patching. This is modified.
   const MemMap* const image_;
   // The bitmap over the image within the heap we are patching. This is not modified.
diff --git a/profman/profman.cc b/profman/profman.cc
index 8f35a76..a42e4f1 100644
--- a/profman/profman.cc
+++ b/profman/profman.cc
@@ -593,7 +593,7 @@
     }
     // Generate the profile data structure.
     ProfileCompilationInfo info;
-    std::vector<MethodReference> methods;  // No methods for now.
+    std::vector<ProfileMethodInfo> methods;  // No methods for now.
     info.AddMethodsAndClasses(methods, resolved_class_set);
     // Write the profile file.
     CHECK(info.Save(fd));
diff --git a/runtime/art_field-inl.h b/runtime/art_field-inl.h
index 80af8e7..16b73c6 100644
--- a/runtime/art_field-inl.h
+++ b/runtime/art_field-inl.h
@@ -311,6 +311,8 @@
 
 template <bool kResolve>
 inline ObjPtr<mirror::Class> ArtField::GetType() {
+  // TODO: Refactor this function into two functions, ResolveType() and LookupType()
+  // so that we can properly annotate it with no-suspension possible / suspension possible.
   const uint32_t field_index = GetDexFieldIndex();
   ObjPtr<mirror::Class> declaring_class = GetDeclaringClass();
   if (UNLIKELY(declaring_class->IsProxyClass())) {
@@ -320,9 +322,16 @@
   const DexFile* const dex_file = dex_cache->GetDexFile();
   const DexFile::FieldId& field_id = dex_file->GetFieldId(field_index);
   ObjPtr<mirror::Class> type = dex_cache->GetResolvedType(field_id.type_idx_);
-  if (kResolve && UNLIKELY(type == nullptr)) {
-    type = ResolveGetType(field_id.type_idx_);
-    CHECK(type != nullptr || Thread::Current()->IsExceptionPending());
+  if (UNLIKELY(type == nullptr)) {
+    ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
+    if (kResolve) {
+      type = class_linker->ResolveType(*dex_file, field_id.type_idx_, declaring_class);
+      CHECK(type != nullptr || Thread::Current()->IsExceptionPending());
+    } else {
+      type = class_linker->LookupResolvedType(
+          *dex_file, field_id.type_idx_, dex_cache, declaring_class->GetClassLoader());
+      DCHECK(!Thread::Current()->IsExceptionPending());
+    }
   }
   return type;
 }
diff --git a/runtime/art_field.cc b/runtime/art_field.cc
index a4a6e5a..7e13104 100644
--- a/runtime/art_field.cc
+++ b/runtime/art_field.cc
@@ -48,10 +48,6 @@
   return Runtime::Current()->GetClassLinker()->FindSystemClass(Thread::Current(), descriptor);
 }
 
-ObjPtr<mirror::Class> ArtField::ResolveGetType(dex::TypeIndex type_idx) {
-  return Runtime::Current()->GetClassLinker()->ResolveType(type_idx, this);
-}
-
 ObjPtr<mirror::String> ArtField::ResolveGetStringName(Thread* self,
                                                       const DexFile& dex_file,
                                                       dex::StringIndex string_idx,
diff --git a/runtime/art_field.h b/runtime/art_field.h
index 427e103..75dd981 100644
--- a/runtime/art_field.h
+++ b/runtime/art_field.h
@@ -217,8 +217,6 @@
  private:
   ObjPtr<mirror::Class> ProxyFindSystemClass(const char* descriptor)
       REQUIRES_SHARED(Locks::mutator_lock_);
-  ObjPtr<mirror::Class> ResolveGetType(dex::TypeIndex type_idx)
-      REQUIRES_SHARED(Locks::mutator_lock_);
   ObjPtr<mirror::String> ResolveGetStringName(Thread* self,
                                               const DexFile& dex_file,
                                               dex::StringIndex string_idx,
diff --git a/runtime/art_method-inl.h b/runtime/art_method-inl.h
index 950f1aa..473d9cf 100644
--- a/runtime/art_method-inl.h
+++ b/runtime/art_method-inl.h
@@ -175,12 +175,19 @@
 }
 
 inline mirror::Class* ArtMethod::GetClassFromTypeIndex(dex::TypeIndex type_idx, bool resolve) {
+  // TODO: Refactor this function into two functions, Resolve...() and Lookup...()
+  // so that we can properly annotate it with no-suspension possible / suspension possible.
   ObjPtr<mirror::DexCache> dex_cache = GetDexCache();
   ObjPtr<mirror::Class> type = dex_cache->GetResolvedType(type_idx);
-  if (UNLIKELY(type == nullptr) && resolve) {
+  if (UNLIKELY(type == nullptr)) {
     ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
-    type = class_linker->ResolveType(type_idx, this);
-    CHECK(type != nullptr || Thread::Current()->IsExceptionPending());
+    if (resolve) {
+      type = class_linker->ResolveType(type_idx, this);
+      CHECK(type != nullptr || Thread::Current()->IsExceptionPending());
+    } else {
+      type = class_linker->LookupResolvedType(
+          *dex_cache->GetDexFile(), type_idx, dex_cache, GetClassLoader());
+    }
   }
   return type.Ptr();
 }
diff --git a/runtime/art_method.h b/runtime/art_method.h
index 3d51fdd..99d7a49 100644
--- a/runtime/art_method.h
+++ b/runtime/art_method.h
@@ -223,13 +223,10 @@
   }
 
   bool IsObsolete() {
-    // TODO Should maybe make this IsIntrinsic check not needed
-    return !IsIntrinsic() && (GetAccessFlags() & kAccObsoleteMethod) != 0;
+    return (GetAccessFlags() & kAccObsoleteMethod) != 0;
   }
 
   void SetIsObsolete() {
-    // TODO We should really support redefining intrinsic if possible.
-    DCHECK(!IsIntrinsic());
     AddAccessFlags(kAccObsoleteMethod);
   }
 
diff --git a/runtime/base/arena_containers.h b/runtime/base/arena_containers.h
index 2c8aa28..62b974e 100644
--- a/runtime/base/arena_containers.h
+++ b/runtime/base/arena_containers.h
@@ -21,6 +21,7 @@
 #include <queue>
 #include <set>
 #include <stack>
+#include <unordered_map>
 #include <utility>
 
 #include "arena_allocator.h"
@@ -85,6 +86,16 @@
                              Pred,
                              ArenaAllocatorAdapter<std::pair<Key, Value>>>;
 
+template <typename Key,
+          typename Value,
+          typename Hash = std::hash<Key>,
+          typename Pred = std::equal_to<Value>>
+using ArenaUnorderedMap = std::unordered_map<Key,
+                                             Value,
+                                             Hash,
+                                             Pred,
+                                             ArenaAllocatorAdapter<std::pair<const Key, Value>>>;
+
 // Implementation details below.
 
 template <bool kCount>
diff --git a/runtime/base/mutex.cc b/runtime/base/mutex.cc
index b93b293..24846e5 100644
--- a/runtime/base/mutex.cc
+++ b/runtime/base/mutex.cc
@@ -46,6 +46,7 @@
 ReaderWriterMutex* Locks::heap_bitmap_lock_ = nullptr;
 Mutex* Locks::instrument_entrypoints_lock_ = nullptr;
 Mutex* Locks::intern_table_lock_ = nullptr;
+Mutex* Locks::jdwp_event_list_lock_ = nullptr;
 Mutex* Locks::jni_function_table_lock_ = nullptr;
 Mutex* Locks::jni_libraries_lock_ = nullptr;
 Mutex* Locks::logging_lock_ = nullptr;
@@ -998,6 +999,7 @@
     DCHECK(verifier_deps_lock_ != nullptr);
     DCHECK(host_dlopen_handles_lock_ != nullptr);
     DCHECK(intern_table_lock_ != nullptr);
+    DCHECK(jdwp_event_list_lock_ != nullptr);
     DCHECK(jni_function_table_lock_ != nullptr);
     DCHECK(jni_libraries_lock_ != nullptr);
     DCHECK(logging_lock_ != nullptr);
@@ -1040,6 +1042,10 @@
     DCHECK(runtime_shutdown_lock_ == nullptr);
     runtime_shutdown_lock_ = new Mutex("runtime shutdown lock", current_lock_level);
 
+    UPDATE_CURRENT_LOCK_LEVEL(kJdwpEventListLock);
+    DCHECK(jdwp_event_list_lock_ == nullptr);
+    jdwp_event_list_lock_ = new Mutex("JDWP event list lock", current_lock_level);
+
     UPDATE_CURRENT_LOCK_LEVEL(kProfilerLock);
     DCHECK(profiler_lock_ == nullptr);
     profiler_lock_ = new Mutex("profiler lock", current_lock_level);
@@ -1167,6 +1173,8 @@
     expected_mutexes_on_weak_ref_access_.push_back(dex_lock_);
     classlinker_classes_lock_->SetShouldRespondToEmptyCheckpointRequest(true);
     expected_mutexes_on_weak_ref_access_.push_back(classlinker_classes_lock_);
+    jdwp_event_list_lock_->SetShouldRespondToEmptyCheckpointRequest(true);
+    expected_mutexes_on_weak_ref_access_.push_back(jdwp_event_list_lock_);
     jni_libraries_lock_->SetShouldRespondToEmptyCheckpointRequest(true);
     expected_mutexes_on_weak_ref_access_.push_back(jni_libraries_lock_);
 
diff --git a/runtime/base/mutex.h b/runtime/base/mutex.h
index 9b6938f..c59664b 100644
--- a/runtime/base/mutex.h
+++ b/runtime/base/mutex.h
@@ -630,8 +630,12 @@
   // Guards shutdown of the runtime.
   static Mutex* runtime_shutdown_lock_ ACQUIRED_AFTER(heap_bitmap_lock_);
 
+  static Mutex* jdwp_event_list_lock_
+      ACQUIRED_AFTER(runtime_shutdown_lock_)
+      ACQUIRED_BEFORE(breakpoint_lock_);
+
   // Guards background profiler global state.
-  static Mutex* profiler_lock_ ACQUIRED_AFTER(runtime_shutdown_lock_);
+  static Mutex* profiler_lock_ ACQUIRED_AFTER(jdwp_event_list_lock_);
 
   // Guards trace (ie traceview) requests.
   static Mutex* trace_lock_ ACQUIRED_AFTER(profiler_lock_);
diff --git a/runtime/class_linker-inl.h b/runtime/class_linker-inl.h
index 3438810..bd510ca 100644
--- a/runtime/class_linker-inl.h
+++ b/runtime/class_linker-inl.h
@@ -78,6 +78,18 @@
   return string.Ptr();
 }
 
+inline ObjPtr<mirror::Class> ClassLinker::LookupResolvedType(
+    dex::TypeIndex type_idx,
+    ObjPtr<mirror::DexCache> dex_cache,
+    ObjPtr<mirror::ClassLoader> class_loader) {
+  ObjPtr<mirror::Class> type = dex_cache->GetResolvedType(type_idx);
+  if (type == nullptr) {
+    type = Runtime::Current()->GetClassLinker()->LookupResolvedType(
+        *dex_cache->GetDexFile(), type_idx, dex_cache, class_loader);
+  }
+  return type;
+}
+
 inline mirror::Class* ClassLinker::ResolveType(dex::TypeIndex type_idx, ArtMethod* referrer) {
   Thread::PoisonObjectPointersIfDebug();
   if (kIsDebugBuild) {
@@ -91,25 +103,6 @@
     Handle<mirror::ClassLoader> class_loader(hs.NewHandle(declaring_class->GetClassLoader()));
     const DexFile& dex_file = *dex_cache->GetDexFile();
     resolved_type = ResolveType(dex_file, type_idx, dex_cache, class_loader);
-    // Note: We cannot check here to see whether we added the type to the cache. The type
-    //       might be an erroneous class, which results in it being hidden from us.
-  }
-  return resolved_type.Ptr();
-}
-
-inline mirror::Class* ClassLinker::ResolveType(dex::TypeIndex type_idx, ArtField* referrer) {
-  Thread::PoisonObjectPointersIfDebug();
-  ObjPtr<mirror::Class> declaring_class = referrer->GetDeclaringClass();
-  ObjPtr<mirror::DexCache> dex_cache_ptr = declaring_class->GetDexCache();
-  ObjPtr<mirror::Class> resolved_type = dex_cache_ptr->GetResolvedType(type_idx);
-  if (UNLIKELY(resolved_type == nullptr)) {
-    StackHandleScope<2> hs(Thread::Current());
-    Handle<mirror::DexCache> dex_cache(hs.NewHandle(dex_cache_ptr));
-    Handle<mirror::ClassLoader> class_loader(hs.NewHandle(declaring_class->GetClassLoader()));
-    const DexFile& dex_file = *dex_cache->GetDexFile();
-    resolved_type = ResolveType(dex_file, type_idx, dex_cache, class_loader);
-    // Note: We cannot check here to see whether we added the type to the cache. The type
-    //       might be an erroneous class, which results in it being hidden from us.
   }
   return resolved_type.Ptr();
 }
diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc
index d02cf17..9b0ffaf 100644
--- a/runtime/class_linker.cc
+++ b/runtime/class_linker.cc
@@ -58,6 +58,7 @@
 #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 "image-inl.h"
 #include "imt_conflict_table.h"
@@ -1156,6 +1157,35 @@
   ClassTable* const table_;
 };
 
+class VerifyDirectInterfacesInTableClassVisitor {
+ public:
+  explicit VerifyDirectInterfacesInTableClassVisitor(ObjPtr<mirror::ClassLoader> class_loader)
+      : class_loader_(class_loader), self_(Thread::Current()) { }
+
+  bool operator()(ObjPtr<mirror::Class> klass) REQUIRES_SHARED(Locks::mutator_lock_) {
+    if (!klass->IsPrimitive() && klass->GetClassLoader() == class_loader_) {
+      classes_.push_back(klass);
+    }
+    return true;
+  }
+
+  void Check() const REQUIRES_SHARED(Locks::mutator_lock_) {
+    for (ObjPtr<mirror::Class> klass : classes_) {
+      for (uint32_t i = 0, num = klass->NumDirectInterfaces(); i != num; ++i) {
+        CHECK(klass->GetDirectInterface(self_, klass, i) != nullptr)
+            << klass->PrettyDescriptor() << " iface #" << i
+            << klass->GetDexFile().StringByTypeIdx(klass->GetDirectInterfaceTypeIdx(i))
+            << " Bug: 34839984";
+      }
+    }
+  }
+
+ private:
+  ObjPtr<mirror::ClassLoader> class_loader_;
+  Thread* self_;
+  std::vector<ObjPtr<mirror::Class>> classes_;
+};
+
 class VerifyDeclaringClassVisitor : public ArtMethodVisitor {
  public:
   VerifyDeclaringClassVisitor() REQUIRES_SHARED(Locks::mutator_lock_, Locks::heap_bitmap_lock_)
@@ -1187,6 +1217,23 @@
   }
 }
 
+template <typename T>
+static void CopyDexCachePairs(const std::atomic<mirror::DexCachePair<T>>* src,
+                              size_t count,
+                              std::atomic<mirror::DexCachePair<T>>* dst) {
+  DCHECK_NE(count, 0u);
+  DCHECK(!src[0].load(std::memory_order_relaxed).object.IsNull() ||
+         src[0].load(std::memory_order_relaxed).index != 0u);
+  for (size_t i = 0; i < count; ++i) {
+    DCHECK_EQ(dst[i].load(std::memory_order_relaxed).index, 0u);
+    DCHECK(dst[i].load(std::memory_order_relaxed).object.IsNull());
+    mirror::DexCachePair<T> source = src[i].load(std::memory_order_relaxed);
+    if (source.index != 0u || !source.object.IsNull()) {
+      dst[i].store(source, std::memory_order_relaxed);
+    }
+  }
+}
+
 bool ClassLinker::UpdateAppImageClassLoadersAndDexCaches(
     gc::space::ImageSpace* space,
     Handle<mirror::ClassLoader> class_loader,
@@ -1240,7 +1287,10 @@
         if (dex_file->NumStringIds() < num_strings) {
           num_strings = dex_file->NumStringIds();
         }
-        const size_t num_types = dex_file->NumTypeIds();
+        size_t num_types = mirror::DexCache::kDexCacheTypeCacheSize;
+        if (dex_file->NumTypeIds() < num_types) {
+          num_types = dex_file->NumTypeIds();
+        }
         const size_t num_methods = dex_file->NumMethodIds();
         const size_t num_fields = dex_file->NumFieldIds();
         size_t num_method_types = mirror::DexCache::kDexCacheMethodTypeCacheSize;
@@ -1260,28 +1310,14 @@
           mirror::StringDexCacheType* const image_resolved_strings = dex_cache->GetStrings();
           mirror::StringDexCacheType* const strings =
               reinterpret_cast<mirror::StringDexCacheType*>(raw_arrays + layout.StringsOffset());
-          for (size_t j = 0; j < num_strings; ++j) {
-            DCHECK_EQ(strings[j].load(std::memory_order_relaxed).index, 0u);
-            DCHECK(strings[j].load(std::memory_order_relaxed).object.IsNull());
-            strings[j].store(image_resolved_strings[j].load(std::memory_order_relaxed),
-                             std::memory_order_relaxed);
-          }
-          mirror::StringDexCachePair::Initialize(strings);
+          CopyDexCachePairs(image_resolved_strings, num_strings, strings);
           dex_cache->SetStrings(strings);
         }
         if (num_types != 0u) {
-          GcRoot<mirror::Class>* const image_resolved_types = dex_cache->GetResolvedTypes();
-          GcRoot<mirror::Class>* const types =
-              reinterpret_cast<GcRoot<mirror::Class>*>(raw_arrays + layout.TypesOffset());
-          for (size_t j = 0; kIsDebugBuild && j < num_types; ++j) {
-            DCHECK(types[j].IsNull());
-          }
-          CopyNonNull(image_resolved_types,
-                      num_types,
-                      types,
-                      [](const GcRoot<mirror::Class>& elem) {
-                          return elem.IsNull();
-                      });
+          mirror::TypeDexCacheType* const image_resolved_types = dex_cache->GetResolvedTypes();
+          mirror::TypeDexCacheType* const types =
+              reinterpret_cast<mirror::TypeDexCacheType*>(raw_arrays + layout.TypesOffset());
+          CopyDexCachePairs(image_resolved_types, num_types, types);
           dex_cache->SetResolvedTypes(types);
         }
         if (num_methods != 0u) {
@@ -1322,15 +1358,7 @@
           mirror::MethodTypeDexCacheType* const method_types =
               reinterpret_cast<mirror::MethodTypeDexCacheType*>(
                   raw_arrays + layout.MethodTypesOffset());
-          for (size_t j = 0; j < num_method_types; ++j) {
-            DCHECK_EQ(method_types[j].load(std::memory_order_relaxed).index, 0u);
-            DCHECK(method_types[j].load(std::memory_order_relaxed).object.IsNull());
-            method_types[j].store(
-                image_resolved_method_types[j].load(std::memory_order_relaxed),
-                std::memory_order_relaxed);
-          }
-
-          mirror::MethodTypeDexCachePair::Initialize(method_types);
+          CopyDexCachePairs(image_resolved_method_types, num_method_types, method_types);
           dex_cache->SetResolvedMethodTypes(method_types);
         }
         if (num_call_sites != 0u) {
@@ -1360,11 +1388,11 @@
       }
       if (kIsDebugBuild) {
         CHECK(new_class_set != nullptr);
-        GcRoot<mirror::Class>* const types = dex_cache->GetResolvedTypes();
+        mirror::TypeDexCacheType* const types = dex_cache->GetResolvedTypes();
         const size_t num_types = dex_cache->NumResolvedTypes();
-        for (int32_t j = 0; j < static_cast<int32_t>(num_types); j++) {
+        for (size_t j = 0; j != num_types; ++j) {
           // The image space is not yet added to the heap, avoid read barriers.
-          ObjPtr<mirror::Class> klass = types[j].Read();
+          ObjPtr<mirror::Class> klass = types[j].load(std::memory_order_relaxed).object.Read();
           if (space->HasAddress(klass.Ptr())) {
             DCHECK(!klass->IsErroneous()) << klass->GetStatus();
             auto it = new_class_set->Find(ClassTable::TableSlot(klass));
@@ -1723,9 +1751,9 @@
       // The current dex file field is bogus, overwrite it so that we can get the dex file in the
       // loop below.
       dex_cache->SetDexFile(dex_file.get());
-      GcRoot<mirror::Class>* const types = dex_cache->GetResolvedTypes();
+      mirror::TypeDexCacheType* const types = dex_cache->GetResolvedTypes();
       for (int32_t j = 0, num_types = dex_cache->NumResolvedTypes(); j < num_types; j++) {
-        ObjPtr<mirror::Class> klass = types[j].Read();
+        ObjPtr<mirror::Class> klass = types[j].load(std::memory_order_relaxed).object.Read();
         if (klass != nullptr) {
           DCHECK(!klass->IsErroneous()) << klass->GetStatus();
         }
@@ -1893,6 +1921,12 @@
     VerifyClassInTableArtMethodVisitor visitor2(class_table);
     header.VisitPackedArtMethods(&visitor2, space->Begin(), kRuntimePointerSize);
   }
+  if (app_image) {
+    // TODO: Restrict this check to debug builds. Bug: 34839984
+    VerifyDirectInterfacesInTableClassVisitor visitor(class_loader.Get());
+    class_table->Visit(visitor);
+    visitor.Check();
+  }
   VLOG(class_linker) << "Adding image space took " << PrettyDuration(NanoTime() - start_time);
   return true;
 }
@@ -1908,6 +1942,13 @@
   const bool tracing_enabled = Trace::IsTracingEnabled();
   Thread* const self = Thread::Current();
   WriterMutexLock mu(self, *Locks::classlinker_classes_lock_);
+  if (kUseReadBarrier) {
+    // We do not track new roots for CC.
+    DCHECK_EQ(0, flags & (kVisitRootFlagNewRoots |
+                          kVisitRootFlagClearRootLog |
+                          kVisitRootFlagStartLoggingNewRoots |
+                          kVisitRootFlagStopLoggingNewRoots));
+  }
   if ((flags & kVisitRootFlagAllRoots) != 0) {
     // Argument for how root visiting deals with ArtField and ArtMethod roots.
     // There is 3 GC cases to handle:
@@ -1937,7 +1978,7 @@
         root.VisitRoot(visitor, RootInfo(kRootVMInternal));
       }
     }
-  } else if ((flags & kVisitRootFlagNewRoots) != 0) {
+  } else if (!kUseReadBarrier && (flags & kVisitRootFlagNewRoots) != 0) {
     for (auto& root : new_class_roots_) {
       ObjPtr<mirror::Class> old_ref = root.Read<kWithoutReadBarrier>();
       root.VisitRoot(visitor, RootInfo(kRootStickyClass));
@@ -1958,13 +1999,13 @@
       }
     }
   }
-  if ((flags & kVisitRootFlagClearRootLog) != 0) {
+  if (!kUseReadBarrier && (flags & kVisitRootFlagClearRootLog) != 0) {
     new_class_roots_.clear();
     new_bss_roots_boot_oat_files_.clear();
   }
-  if ((flags & kVisitRootFlagStartLoggingNewRoots) != 0) {
+  if (!kUseReadBarrier && (flags & kVisitRootFlagStartLoggingNewRoots) != 0) {
     log_new_roots_ = true;
-  } else if ((flags & kVisitRootFlagStopLoggingNewRoots) != 0) {
+  } else if (!kUseReadBarrier && (flags & kVisitRootFlagStopLoggingNewRoots) != 0) {
     log_new_roots_ = false;
   }
   // We deliberately ignore the class roots in the image since we
@@ -3430,6 +3471,11 @@
     return nullptr;
   }
   table->InsertStrongRoot(h_dex_cache.Get());
+  if (h_class_loader.Get() != nullptr) {
+    // Since we added a strong root to the class table, do the write barrier as required for
+    // remembered sets and generational GCs.
+    Runtime::Current()->GetHeap()->WriteBarrierEveryFieldOf(h_class_loader.Get());
+  }
   return h_dex_cache.Get();
 }
 
@@ -4501,6 +4547,108 @@
   return CanWeInitializeClass(super_class, can_init_statics, can_init_parents);
 }
 
+std::string DescribeSpace(ObjPtr<mirror::Class> klass) REQUIRES_SHARED(Locks::mutator_lock_) {
+  std::ostringstream oss;
+  gc::Heap* heap = Runtime::Current()->GetHeap();
+  gc::space::ContinuousSpace* cs = heap->FindContinuousSpaceFromAddress(klass.Ptr());
+  if (cs != nullptr) {
+    if (cs->IsImageSpace()) {
+      oss << "image/" << cs->GetName() << "/" << cs->AsImageSpace()->GetImageFilename();
+    } else {
+      oss << "continuous/" << cs->GetName();
+    }
+  } else {
+    gc::space::DiscontinuousSpace* ds =
+        heap->FindDiscontinuousSpaceFromObject(klass, /* fail_ok */ true);
+    if (ds != nullptr) {
+      oss << "discontinuous/" << ds->GetName();
+    } else {
+      oss << "invalid";
+    }
+  }
+  return oss.str();
+}
+
+std::string DescribeLoaders(ObjPtr<mirror::Class> klass, const char* iface_descriptor)
+    REQUIRES_SHARED(Locks::mutator_lock_) {
+  std::ostringstream oss;
+  uint32_t hash = ComputeModifiedUtf8Hash(iface_descriptor);
+  ScopedObjectAccessUnchecked soa(Thread::Current());
+  ObjPtr<mirror::Class> path_class_loader =
+      soa.Decode<mirror::Class>(WellKnownClasses::dalvik_system_PathClassLoader);
+  ObjPtr<mirror::Class> dex_class_loader =
+      soa.Decode<mirror::Class>(WellKnownClasses::dalvik_system_DexClassLoader);
+
+  // Print the class loader chain.
+  bool found_iface;
+  const char* loader_separator = "";
+  for (ObjPtr<mirror::ClassLoader> loader = klass->GetClassLoader();
+       loader != nullptr;
+       loader = loader->GetParent()) {
+    oss << loader_separator << loader->GetClass()->PrettyDescriptor();
+    loader_separator = ";";
+    // If we didn't find the interface yet, try to find it in the current class loader.
+    if (!found_iface) {
+      ClassTable* table = Runtime::Current()->GetClassLinker()->ClassTableForClassLoader(loader);
+      ObjPtr<mirror::Class> iface =
+          (table != nullptr) ? table->Lookup(iface_descriptor, hash) : nullptr;
+      if (iface != nullptr) {
+        found_iface = true;
+        oss << "[hit:" << DescribeSpace(iface) << "]";
+      }
+    }
+
+    // For PathClassLoader or DexClassLoader also dump the dex file locations.
+    if (loader->GetClass() == path_class_loader || loader->GetClass() == dex_class_loader) {
+      ArtField* const cookie_field =
+          jni::DecodeArtField(WellKnownClasses::dalvik_system_DexFile_cookie);
+      ArtField* const dex_file_field =
+          jni::DecodeArtField(WellKnownClasses::dalvik_system_DexPathList__Element_dexFile);
+      ObjPtr<mirror::Object> dex_path_list =
+          jni::DecodeArtField(WellKnownClasses::dalvik_system_BaseDexClassLoader_pathList)->
+              GetObject(loader);
+      if (dex_path_list != nullptr && dex_file_field != nullptr && cookie_field != nullptr) {
+        ObjPtr<mirror::Object> dex_elements_obj =
+            jni::DecodeArtField(WellKnownClasses::dalvik_system_DexPathList_dexElements)->
+            GetObject(dex_path_list);
+        if (dex_elements_obj != nullptr) {
+          ObjPtr<mirror::ObjectArray<mirror::Object>> dex_elements =
+              dex_elements_obj->AsObjectArray<mirror::Object>();
+          oss << "(";
+          const char* path_separator = "";
+          for (int32_t i = 0; i != dex_elements->GetLength(); ++i) {
+            ObjPtr<mirror::Object> element = dex_elements->GetWithoutChecks(i);
+            ObjPtr<mirror::Object> dex_file =
+                (element != nullptr) ? dex_file_field->GetObject(element) : nullptr;
+            ObjPtr<mirror::LongArray> long_array =
+                (dex_file != nullptr) ? cookie_field->GetObject(dex_file)->AsLongArray() : nullptr;
+            if (long_array != nullptr) {
+              int32_t long_array_size = long_array->GetLength();
+              // First element is the oat file.
+              for (int32_t j = kDexFileIndexStart; j < long_array_size; ++j) {
+                const DexFile* cp_dex_file = reinterpret_cast<const DexFile*>(
+                    static_cast<uintptr_t>(long_array->GetWithoutChecks(j)));
+                oss << path_separator << cp_dex_file->GetLocation();
+                path_separator = ":";
+              }
+            }
+          }
+          oss << ")";
+        }
+      }
+    }
+  }
+
+  // Do a paranoid check that the `klass` itself is in the class table.
+  ClassTable* table =
+      Runtime::Current()->GetClassLinker()->ClassTableForClassLoader(klass->GetClassLoader());
+  ObjPtr<mirror::Class> k = (table != nullptr) ? table->LookupByDescriptor(klass) : nullptr;
+  if (k != klass) {
+    oss << "{FAIL:" << k.Ptr() << "!=" << klass.Ptr() << "}";
+  }
+  return oss.str();
+}
+
 bool ClassLinker::InitializeClass(Thread* self, Handle<mirror::Class> klass,
                                   bool can_init_statics, bool can_init_parents) {
   // see JLS 3rd edition, 12.4.2 "Detailed Initialization Procedure" for the locking protocol
@@ -4648,7 +4796,15 @@
       MutableHandle<mirror::Class> handle_scope_iface(hs_iface.NewHandle<mirror::Class>(nullptr));
       for (size_t i = 0; i < num_direct_interfaces; i++) {
         handle_scope_iface.Assign(mirror::Class::GetDirectInterface(self, klass.Get(), i));
-        CHECK(handle_scope_iface != nullptr);
+        if (UNLIKELY(handle_scope_iface == nullptr)) {
+          const char* iface_descriptor =
+              klass->GetDexFile().StringByTypeIdx(klass->GetDirectInterfaceTypeIdx(i));
+          LOG(FATAL) << "Check failed: handle_scope_iface != nullptr "
+              << "Debug data for bug 34839984: "
+              << klass->PrettyDescriptor() << " iface #" << i << " " << iface_descriptor
+              << " space: " << DescribeSpace(klass.Get())
+              << " loaders: " << DescribeLoaders(klass.Get(), iface_descriptor);
+        }
         CHECK(handle_scope_iface->IsInterface());
         if (handle_scope_iface->HasBeenRecursivelyInitialized()) {
           // We have already done this for this interface. Skip it.
@@ -7759,7 +7915,9 @@
   uint32_t utf16_length;
   const char* utf8_data = dex_file.StringDataAndUtf16LengthByIdx(string_idx, &utf16_length);
   ObjPtr<mirror::String> string = intern_table_->InternStrong(utf16_length, utf8_data);
-  dex_cache->SetResolvedString(string_idx, string);
+  if (string != nullptr) {
+    dex_cache->SetResolvedString(string_idx, string);
+  }
   return string.Ptr();
 }
 
@@ -7800,11 +7958,16 @@
       // Find the class in the loaded classes table.
       type = LookupClass(self, descriptor, hash, class_loader.Ptr());
     }
+    if (type != nullptr) {
+      if (type->IsResolved()) {
+        dex_cache->SetResolvedType(type_idx, type);
+      } else {
+        type = nullptr;
+      }
+    }
   }
-  if (type != nullptr && type->IsResolved()) {
-    return type.Ptr();
-  }
-  return nullptr;
+  DCHECK(type == nullptr || type->IsResolved());
+  return type;
 }
 
 mirror::Class* ClassLinker::ResolveType(const DexFile& dex_file,
@@ -7824,6 +7987,12 @@
   Thread::PoisonObjectPointersIfDebug();
   ObjPtr<mirror::Class> resolved = dex_cache->GetResolvedType(type_idx);
   if (resolved == nullptr) {
+    // TODO: Avoid this lookup as it duplicates work done in FindClass(). It is here
+    // as a workaround for FastNative JNI to avoid AssertNoPendingException() when
+    // trying to resolve annotations while an exception may be pending. Bug: 34659969
+    resolved = LookupResolvedType(dex_file, type_idx, dex_cache.Get(), class_loader.Get());
+  }
+  if (resolved == nullptr) {
     Thread* self = Thread::Current();
     const char* descriptor = dex_file.StringByTypeIdx(type_idx);
     resolved = FindClass(self, descriptor, class_loader);
diff --git a/runtime/class_linker.h b/runtime/class_linker.h
index e27a53d..33eed3c 100644
--- a/runtime/class_linker.h
+++ b/runtime/class_linker.h
@@ -266,10 +266,6 @@
       REQUIRES_SHARED(Locks::mutator_lock_)
       REQUIRES(!Locks::dex_lock_, !Roles::uninterruptible_);
 
-  mirror::Class* ResolveType(dex::TypeIndex type_idx, ArtField* referrer)
-      REQUIRES_SHARED(Locks::mutator_lock_)
-      REQUIRES(!Locks::dex_lock_, !Roles::uninterruptible_);
-
   // Look up a resolved type with the given ID from the DexFile. The ClassLoader is used to search
   // for the type, since it may be referenced from but not contained within the given DexFile.
   ObjPtr<mirror::Class> LookupResolvedType(const DexFile& dex_file,
@@ -277,6 +273,10 @@
                                            ObjPtr<mirror::DexCache> dex_cache,
                                            ObjPtr<mirror::ClassLoader> class_loader)
       REQUIRES_SHARED(Locks::mutator_lock_);
+  static ObjPtr<mirror::Class> LookupResolvedType(dex::TypeIndex type_idx,
+                                                  ObjPtr<mirror::DexCache> dex_cache,
+                                                  ObjPtr<mirror::ClassLoader> class_loader)
+      REQUIRES_SHARED(Locks::mutator_lock_);
 
   // Resolve a type with the given ID from the DexFile, storing the
   // result in DexCache. The ClassLoader is used to search for the
diff --git a/runtime/class_linker_test.cc b/runtime/class_linker_test.cc
index 07f3744..21cdede 100644
--- a/runtime/class_linker_test.cc
+++ b/runtime/class_linker_test.cc
@@ -935,7 +935,7 @@
       class_linker_->LookupResolvedType(dex_file, type_idx, dex_cache, class_loader.Get()),
       klass);
   // Zero out the resolved type and make sure LookupResolvedType still finds it.
-  dex_cache->SetResolvedType(type_idx, nullptr);
+  dex_cache->ClearResolvedType(type_idx);
   EXPECT_TRUE(dex_cache->GetResolvedType(type_idx) == nullptr);
   EXPECT_OBJ_PTR_EQ(
       class_linker_->LookupResolvedType(dex_file, type_idx, dex_cache, class_loader.Get()),
@@ -970,7 +970,7 @@
       class_linker_->LookupResolvedType(dex_file, array_idx, dex_cache.Get(), class_loader.Get()),
       array_klass);
   // Zero out the resolved type and make sure LookupResolvedType() still finds it.
-  dex_cache->SetResolvedType(array_idx, nullptr);
+  dex_cache->ClearResolvedType(array_idx);
   EXPECT_TRUE(dex_cache->GetResolvedType(array_idx) == nullptr);
   EXPECT_OBJ_PTR_EQ(
       class_linker_->LookupResolvedType(dex_file, array_idx, dex_cache.Get(), class_loader.Get()),
@@ -993,7 +993,7 @@
       class_linker_->LookupResolvedType(dex_file, type_idx, dex_cache.Get(), class_loader.Get()),
       klass.Get());
   // Zero out the resolved type and make sure LookupResolvedType still finds it.
-  dex_cache->SetResolvedType(type_idx, nullptr);
+  dex_cache->ClearResolvedType(type_idx);
   EXPECT_TRUE(dex_cache->GetResolvedType(type_idx) == nullptr);
   EXPECT_OBJ_PTR_EQ(
       class_linker_->LookupResolvedType(dex_file, type_idx, dex_cache.Get(), class_loader.Get()),
@@ -1011,7 +1011,7 @@
       class_linker_->LookupResolvedType(dex_file, type_idx, dex_cache.Get(), class_loader.Get()),
       klass.Get());
   // Zero out the resolved type and make sure LookupResolvedType() still finds it.
-  dex_cache->SetResolvedType(type_idx, nullptr);
+  dex_cache->ClearResolvedType(type_idx);
   EXPECT_TRUE(dex_cache->GetResolvedType(type_idx) == nullptr);
   EXPECT_OBJ_PTR_EQ(
       class_linker_->LookupResolvedType(dex_file, type_idx, dex_cache.Get(), class_loader.Get()),
diff --git a/runtime/dexopt_test.cc b/runtime/dexopt_test.cc
index 69c6151..5167869 100644
--- a/runtime/dexopt_test.cc
+++ b/runtime/dexopt_test.cc
@@ -70,6 +70,11 @@
   // rather than use this flag.
   args.push_back("-Xnorelocate");
 
+  ScratchFile profile_file;
+  if (CompilerFilter::DependsOnProfile(filter)) {
+    args.push_back("--profile-file=" + profile_file.GetFilename());
+  }
+
   if (pic) {
     args.push_back("--compile-pic");
   }
diff --git a/runtime/entrypoints/entrypoint_utils-inl.h b/runtime/entrypoints/entrypoint_utils-inl.h
index 28aca6c..3bc49b8 100644
--- a/runtime/entrypoints/entrypoint_utils-inl.h
+++ b/runtime/entrypoints/entrypoint_utils-inl.h
@@ -709,10 +709,10 @@
     return resolved_method;
   } else if (type == kSuper) {
     // TODO This lookup is rather slow.
-    dex::TypeIndex method_type_idx =
-        referrer->GetDexFile()->GetMethodId(method_idx).class_idx_;
-    mirror::Class* method_reference_class =
-        referrer->GetDexCache()->GetResolvedType(method_type_idx);
+    ObjPtr<mirror::DexCache> dex_cache = referrer->GetDexCache();
+    dex::TypeIndex method_type_idx = dex_cache->GetDexFile()->GetMethodId(method_idx).class_idx_;
+    ObjPtr<mirror::Class> method_reference_class = ClassLinker::LookupResolvedType(
+        method_type_idx, dex_cache, referrer->GetClassLoader());
     if (method_reference_class == nullptr) {
       // Need to do full type resolution...
       return nullptr;
diff --git a/runtime/entrypoints/quick/quick_dexcache_entrypoints.cc b/runtime/entrypoints/quick/quick_dexcache_entrypoints.cc
index 699cf91..355d7b3 100644
--- a/runtime/entrypoints/quick/quick_dexcache_entrypoints.cc
+++ b/runtime/entrypoints/quick/quick_dexcache_entrypoints.cc
@@ -32,17 +32,22 @@
 namespace art {
 
 static inline void BssWriteBarrier(ArtMethod* outer_method) REQUIRES_SHARED(Locks::mutator_lock_) {
-  // For AOT code, we need a write barrier for the class loader that holds
-  // the GC roots in the .bss.
+  // For AOT code, we need a write barrier for the class loader that holds the
+  // GC roots in the .bss.
   const DexFile* dex_file = outer_method->GetDexFile();
   if (dex_file != nullptr &&
       dex_file->GetOatDexFile() != nullptr &&
       !dex_file->GetOatDexFile()->GetOatFile()->GetBssGcRoots().empty()) {
-    mirror::ClassLoader* class_loader = outer_method->GetClassLoader();
-    if (class_loader != nullptr) {
-      DCHECK(!class_loader->GetClassTable()->InsertOatFile(dex_file->GetOatDexFile()->GetOatFile()))
+    ObjPtr<mirror::ClassLoader> class_loader = outer_method->GetClassLoader();
+    if (kIsDebugBuild) {
+      ClassTable* class_table =
+          Runtime::Current()->GetClassLinker()->ClassTableForClassLoader(class_loader);
+      CHECK(class_table != nullptr &&
+            !class_table->InsertOatFile(dex_file->GetOatDexFile()->GetOatFile()))
           << "Oat file with .bss GC roots was not registered in class table: "
           << dex_file->GetOatDexFile()->GetOatFile()->GetLocation();
+    }
+    if (class_loader != nullptr) {
       // Note that we emit the barrier before the compiled code stores the String or Class
       // as a GC root. This is OK as there is no suspend point point in between.
       Runtime::Current()->GetHeap()->WriteBarrierEveryFieldOf(class_loader);
diff --git a/runtime/gc/accounting/mod_union_table.cc b/runtime/gc/accounting/mod_union_table.cc
index 0325535..a5bb91a 100644
--- a/runtime/gc/accounting/mod_union_table.cc
+++ b/runtime/gc/accounting/mod_union_table.cc
@@ -327,7 +327,7 @@
 class EmptyMarkObjectVisitor : public MarkObjectVisitor {
  public:
   mirror::Object* MarkObject(mirror::Object* obj) OVERRIDE {return obj;}
-  void MarkHeapReference(mirror::HeapReference<mirror::Object>*) OVERRIDE {}
+  void MarkHeapReference(mirror::HeapReference<mirror::Object>*, bool) OVERRIDE {}
 };
 
 void ModUnionTable::FilterCards() {
@@ -459,7 +459,7 @@
     for (mirror::HeapReference<mirror::Object>* obj_ptr : references) {
       if (obj_ptr->AsMirrorPtr() != nullptr) {
         all_null = false;
-        visitor->MarkHeapReference(obj_ptr);
+        visitor->MarkHeapReference(obj_ptr, /*do_atomic_update*/ false);
       }
     }
     count += references.size();
diff --git a/runtime/gc/accounting/mod_union_table_test.cc b/runtime/gc/accounting/mod_union_table_test.cc
index cf63b30..48a8742 100644
--- a/runtime/gc/accounting/mod_union_table_test.cc
+++ b/runtime/gc/accounting/mod_union_table_test.cc
@@ -97,7 +97,8 @@
 class CollectVisitedVisitor : public MarkObjectVisitor {
  public:
   explicit CollectVisitedVisitor(std::set<mirror::Object*>* out) : out_(out) {}
-  virtual void MarkHeapReference(mirror::HeapReference<mirror::Object>* ref) OVERRIDE
+  virtual void MarkHeapReference(mirror::HeapReference<mirror::Object>* ref,
+                                 bool do_atomic_update ATTRIBUTE_UNUSED) OVERRIDE
       REQUIRES_SHARED(Locks::mutator_lock_) {
     DCHECK(ref != nullptr);
     MarkObject(ref->AsMirrorPtr());
diff --git a/runtime/gc/accounting/remembered_set.cc b/runtime/gc/accounting/remembered_set.cc
index 29bab01..7b1e2b8 100644
--- a/runtime/gc/accounting/remembered_set.cc
+++ b/runtime/gc/accounting/remembered_set.cc
@@ -74,7 +74,7 @@
     mirror::HeapReference<mirror::Object>* ref_ptr = obj->GetFieldObjectReferenceAddr(offset);
     if (target_space_->HasAddress(ref_ptr->AsMirrorPtr())) {
       *contains_reference_to_target_space_ = true;
-      collector_->MarkHeapReference(ref_ptr);
+      collector_->MarkHeapReference(ref_ptr, /*do_atomic_update*/ false);
       DCHECK(!target_space_->HasAddress(ref_ptr->AsMirrorPtr()));
     }
   }
diff --git a/runtime/gc/collector/concurrent_copying.cc b/runtime/gc/collector/concurrent_copying.cc
index f18ffb4..8f9c187 100644
--- a/runtime/gc/collector/concurrent_copying.cc
+++ b/runtime/gc/collector/concurrent_copying.cc
@@ -53,6 +53,8 @@
 // Slow path mark stack size, increase this if the stack is getting full and it is causing
 // performance problems.
 static constexpr size_t kReadBarrierMarkStackSize = 512 * KB;
+// Verify that there are no missing card marks.
+static constexpr bool kVerifyNoMissingCardMarks = kIsDebugBuild;
 
 ConcurrentCopying::ConcurrentCopying(Heap* heap,
                                      const std::string& name_prefix,
@@ -109,12 +111,29 @@
   }
 }
 
-void ConcurrentCopying::MarkHeapReference(mirror::HeapReference<mirror::Object>* from_ref) {
-  // Used for preserving soft references, should be OK to not have a CAS here since there should be
-  // no other threads which can trigger read barriers on the same referent during reference
-  // processing.
-  from_ref->Assign(Mark(from_ref->AsMirrorPtr()));
-  DCHECK(!from_ref->IsNull());
+void ConcurrentCopying::MarkHeapReference(mirror::HeapReference<mirror::Object>* field,
+                                          bool do_atomic_update) {
+  if (UNLIKELY(do_atomic_update)) {
+    // Used to mark the referent in DelayReferenceReferent in transaction mode.
+    mirror::Object* from_ref = field->AsMirrorPtr();
+    if (from_ref == nullptr) {
+      return;
+    }
+    mirror::Object* to_ref = Mark(from_ref);
+    if (from_ref != to_ref) {
+      do {
+        if (field->AsMirrorPtr() != from_ref) {
+          // Concurrently overwritten by a mutator.
+          break;
+        }
+      } while (!field->CasWeakRelaxed(from_ref, to_ref));
+    }
+  } else {
+    // Used for preserving soft references, should be OK to not have a CAS here since there should be
+    // no other threads which can trigger read barriers on the same referent during reference
+    // processing.
+    field->Assign(Mark(field->AsMirrorPtr()));
+  }
 }
 
 ConcurrentCopying::~ConcurrentCopying() {
@@ -138,7 +157,7 @@
     MarkingPhase();
   }
   // Verify no from space refs. This causes a pause.
-  if (kEnableNoFromSpaceRefsVerification || kIsDebugBuild) {
+  if (kEnableNoFromSpaceRefsVerification) {
     TimingLogger::ScopedTiming split("(Paused)VerifyNoFromSpaceReferences", GetTimings());
     ScopedPause pause(this, false);
     CheckEmptyMarkStack();
@@ -318,6 +337,9 @@
     TimingLogger::ScopedTiming split("(Paused)FlipCallback", cc->GetTimings());
     // Note: self is not necessarily equal to thread since thread may be suspended.
     Thread* self = Thread::Current();
+    if (kVerifyNoMissingCardMarks) {
+      cc->VerifyNoMissingCardMarks();
+    }
     CHECK(thread == self);
     Locks::mutator_lock_->AssertExclusiveHeld(self);
     cc->region_space_->SetFromSpace(cc->rb_table_, cc->force_evacuate_all_);
@@ -428,6 +450,72 @@
   }
 }
 
+class ConcurrentCopying::VerifyNoMissingCardMarkVisitor {
+ public:
+  VerifyNoMissingCardMarkVisitor(ConcurrentCopying* cc, ObjPtr<mirror::Object> holder)
+    : cc_(cc),
+      holder_(holder) {}
+
+  void operator()(ObjPtr<mirror::Object> obj,
+                  MemberOffset offset,
+                  bool is_static ATTRIBUTE_UNUSED) const
+      REQUIRES_SHARED(Locks::mutator_lock_) ALWAYS_INLINE {
+    if (offset.Uint32Value() != mirror::Object::ClassOffset().Uint32Value()) {
+     CheckReference(obj->GetFieldObject<mirror::Object, kDefaultVerifyFlags, kWithoutReadBarrier>(
+         offset), offset.Uint32Value());
+    }
+  }
+  void operator()(ObjPtr<mirror::Class> klass,
+                  ObjPtr<mirror::Reference> ref) const
+      REQUIRES_SHARED(Locks::mutator_lock_) ALWAYS_INLINE {
+    CHECK(klass->IsTypeOfReferenceClass());
+    this->operator()(ref, mirror::Reference::ReferentOffset(), false);
+  }
+
+  void VisitRootIfNonNull(mirror::CompressedReference<mirror::Object>* root) const
+      REQUIRES_SHARED(Locks::mutator_lock_) {
+    if (!root->IsNull()) {
+      VisitRoot(root);
+    }
+  }
+
+  void VisitRoot(mirror::CompressedReference<mirror::Object>* root) const
+      REQUIRES_SHARED(Locks::mutator_lock_) {
+    CheckReference(root->AsMirrorPtr());
+  }
+
+  void CheckReference(mirror::Object* ref, int32_t offset = -1) const
+      REQUIRES_SHARED(Locks::mutator_lock_) {
+    CHECK(ref == nullptr || !cc_->region_space_->IsInNewlyAllocatedRegion(ref))
+        << holder_->PrettyTypeOf() << "(" << holder_.Ptr() << ") references object "
+        << ref->PrettyTypeOf() << "(" << ref << ") in newly allocated region at offset=" << offset;
+  }
+
+ private:
+  ConcurrentCopying* const cc_;
+  ObjPtr<mirror::Object> const holder_;
+};
+
+void ConcurrentCopying::VerifyNoMissingCardMarkCallback(mirror::Object* obj, void* arg) {
+  auto* collector = reinterpret_cast<ConcurrentCopying*>(arg);
+  // Objects not on dirty cards should never have references to newly allocated regions.
+  if (!collector->heap_->GetCardTable()->IsDirty(obj)) {
+    VerifyNoMissingCardMarkVisitor visitor(collector, /*holder*/ obj);
+    obj->VisitReferences</*kVisitNativeRoots*/true, kVerifyNone, kWithoutReadBarrier>(
+        visitor,
+        visitor);
+  }
+}
+
+void ConcurrentCopying::VerifyNoMissingCardMarks() {
+  TimingLogger::ScopedTiming split(__FUNCTION__, GetTimings());
+  region_space_->Walk(&VerifyNoMissingCardMarkCallback, this);
+  {
+    ReaderMutexLock rmu(Thread::Current(), *Locks::heap_bitmap_lock_);
+    heap_->GetLiveBitmap()->Walk(&VerifyNoMissingCardMarkCallback, this);
+  }
+}
+
 // Switch threads that from from-space to to-space refs. Forward/mark the thread roots.
 void ConcurrentCopying::FlipThreadRoots() {
   TimingLogger::ScopedTiming split("FlipThreadRoots", GetTimings());
@@ -1380,7 +1468,7 @@
     size_t alloc_size = RoundUp(obj_size, space::RegionSpace::kAlignment);
     region_space_->AddLiveBytes(to_ref, alloc_size);
   }
-  if (ReadBarrier::kEnableToSpaceInvariantChecks || kIsDebugBuild) {
+  if (ReadBarrier::kEnableToSpaceInvariantChecks) {
     AssertToSpaceInvariantObjectVisitor visitor(this);
     visitor(to_ref);
   }
@@ -2330,7 +2418,9 @@
     MutexLock mu(self, mark_stack_lock_);
     CHECK_EQ(pooled_mark_stacks_.size(), kMarkStackPoolSize);
   }
-  {
+  // kVerifyNoMissingCardMarks relies on the region space cards not being cleared to avoid false
+  // positives.
+  if (!kVerifyNoMissingCardMarks) {
     TimingLogger::ScopedTiming split("ClearRegionSpaceCards", GetTimings());
     // We do not currently use the region space cards at all, madvise them away to save ram.
     heap_->GetCardTable()->ClearCardRange(region_space_->Begin(), region_space_->Limit());
diff --git a/runtime/gc/collector/concurrent_copying.h b/runtime/gc/collector/concurrent_copying.h
index 844bb45..a0da9fc 100644
--- a/runtime/gc/collector/concurrent_copying.h
+++ b/runtime/gc/collector/concurrent_copying.h
@@ -162,6 +162,12 @@
   void VerifyGrayImmuneObjects()
       REQUIRES(Locks::mutator_lock_)
       REQUIRES(!mark_stack_lock_);
+  static void VerifyNoMissingCardMarkCallback(mirror::Object* obj, void* arg)
+      REQUIRES(Locks::mutator_lock_)
+      REQUIRES(!mark_stack_lock_);
+  void VerifyNoMissingCardMarks()
+      REQUIRES(Locks::mutator_lock_)
+      REQUIRES(!mark_stack_lock_);
   size_t ProcessThreadLocalMarkStacks(bool disable_weak_ref_access, Closure* checkpoint_callback)
       REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!mark_stack_lock_);
   void RevokeThreadLocalMarkStacks(bool disable_weak_ref_access, Closure* checkpoint_callback)
@@ -176,7 +182,8 @@
   virtual mirror::Object* MarkObject(mirror::Object* from_ref) OVERRIDE
       REQUIRES_SHARED(Locks::mutator_lock_)
       REQUIRES(!mark_stack_lock_, !skipped_blocks_lock_, !immune_gray_stack_lock_);
-  virtual void MarkHeapReference(mirror::HeapReference<mirror::Object>* from_ref) OVERRIDE
+  virtual void MarkHeapReference(mirror::HeapReference<mirror::Object>* from_ref,
+                                 bool do_atomic_update) OVERRIDE
       REQUIRES_SHARED(Locks::mutator_lock_)
       REQUIRES(!mark_stack_lock_, !skipped_blocks_lock_, !immune_gray_stack_lock_);
   virtual mirror::Object* IsMarked(mirror::Object* from_ref) OVERRIDE
@@ -329,6 +336,7 @@
   class VerifyNoFromSpaceRefsFieldVisitor;
   class VerifyNoFromSpaceRefsObjectVisitor;
   class VerifyNoFromSpaceRefsVisitor;
+  class VerifyNoMissingCardMarkVisitor;
 
   DISALLOW_IMPLICIT_CONSTRUCTORS(ConcurrentCopying);
 };
diff --git a/runtime/gc/collector/garbage_collector.cc b/runtime/gc/collector/garbage_collector.cc
index 14fd332..1e4196b 100644
--- a/runtime/gc/collector/garbage_collector.cc
+++ b/runtime/gc/collector/garbage_collector.cc
@@ -65,7 +65,8 @@
       name_(name),
       pause_histogram_((name_ + " paused").c_str(), kPauseBucketSize, kPauseBucketCount),
       cumulative_timings_(name),
-      pause_histogram_lock_("pause histogram lock", kDefaultMutexLevel, true) {
+      pause_histogram_lock_("pause histogram lock", kDefaultMutexLevel, true),
+      is_transaction_active_(false) {
   ResetCumulativeStatistics();
 }
 
@@ -88,6 +89,9 @@
   uint64_t start_time = NanoTime();
   Iteration* current_iteration = GetCurrentIteration();
   current_iteration->Reset(gc_cause, clear_soft_references);
+  // Note transaction mode is single-threaded and there's no asynchronous GC and this flag doesn't
+  // change in the middle of a GC.
+  is_transaction_active_ = Runtime::Current()->IsActiveTransaction();
   RunPhases();  // Run all the GC phases.
   // Add the current timings to the cumulative timings.
   cumulative_timings_.AddLogger(*GetTimings());
@@ -109,6 +113,7 @@
     MutexLock mu(self, pause_histogram_lock_);
     pause_histogram_.AdjustAndAddValue(pause_time);
   }
+  is_transaction_active_ = false;
 }
 
 void GarbageCollector::SwapBitmaps() {
diff --git a/runtime/gc/collector/garbage_collector.h b/runtime/gc/collector/garbage_collector.h
index 95601d7..14d0499 100644
--- a/runtime/gc/collector/garbage_collector.h
+++ b/runtime/gc/collector/garbage_collector.h
@@ -199,12 +199,17 @@
   // Force mark an object.
   virtual mirror::Object* MarkObject(mirror::Object* obj)
       REQUIRES_SHARED(Locks::mutator_lock_) = 0;
-  virtual void MarkHeapReference(mirror::HeapReference<mirror::Object>* obj)
+  virtual void MarkHeapReference(mirror::HeapReference<mirror::Object>* obj,
+                                 bool do_atomic_update)
       REQUIRES_SHARED(Locks::mutator_lock_) = 0;
   virtual void DelayReferenceReferent(ObjPtr<mirror::Class> klass,
                                       ObjPtr<mirror::Reference> reference)
       REQUIRES_SHARED(Locks::mutator_lock_) = 0;
 
+  bool IsTransactionActive() const {
+    return is_transaction_active_;
+  }
+
  protected:
   // Run all of the GC phases.
   virtual void RunPhases() = 0;
@@ -223,6 +228,7 @@
   int64_t total_freed_bytes_;
   CumulativeLogger cumulative_timings_;
   mutable Mutex pause_histogram_lock_ DEFAULT_MUTEX_ACQUIRED_AFTER;
+  bool is_transaction_active_;
 
  private:
   DISALLOW_IMPLICIT_CONSTRUCTORS(GarbageCollector);
diff --git a/runtime/gc/collector/mark_compact.cc b/runtime/gc/collector/mark_compact.cc
index 85e6783..0039388 100644
--- a/runtime/gc/collector/mark_compact.cc
+++ b/runtime/gc/collector/mark_compact.cc
@@ -260,7 +260,8 @@
   mark_stack_->PushBack(obj);
 }
 
-void MarkCompact::MarkHeapReference(mirror::HeapReference<mirror::Object>* obj_ptr) {
+void MarkCompact::MarkHeapReference(mirror::HeapReference<mirror::Object>* obj_ptr,
+                                    bool do_atomic_update ATTRIBUTE_UNUSED) {
   if (updating_references_) {
     UpdateHeapReference(obj_ptr);
   } else {
diff --git a/runtime/gc/collector/mark_compact.h b/runtime/gc/collector/mark_compact.h
index 6d52d5d..85727c2 100644
--- a/runtime/gc/collector/mark_compact.h
+++ b/runtime/gc/collector/mark_compact.h
@@ -170,7 +170,8 @@
   // Mark a single object.
   virtual mirror::Object* MarkObject(mirror::Object* obj) OVERRIDE
       REQUIRES(Locks::heap_bitmap_lock_, Locks::mutator_lock_);
-  virtual void MarkHeapReference(mirror::HeapReference<mirror::Object>* obj_ptr) OVERRIDE
+  virtual void MarkHeapReference(mirror::HeapReference<mirror::Object>* obj_ptr,
+                                 bool do_atomic_update) OVERRIDE
       REQUIRES(Locks::heap_bitmap_lock_, Locks::mutator_lock_);
   virtual mirror::Object* IsMarked(mirror::Object* obj) OVERRIDE
       REQUIRES_SHARED(Locks::heap_bitmap_lock_)
diff --git a/runtime/gc/collector/mark_sweep.cc b/runtime/gc/collector/mark_sweep.cc
index f00da73..f591cf0 100644
--- a/runtime/gc/collector/mark_sweep.cc
+++ b/runtime/gc/collector/mark_sweep.cc
@@ -532,7 +532,8 @@
   return !mark_bitmap_->AtomicTestAndSet(obj, visitor);
 }
 
-void MarkSweep::MarkHeapReference(mirror::HeapReference<mirror::Object>* ref) {
+void MarkSweep::MarkHeapReference(mirror::HeapReference<mirror::Object>* ref,
+                                  bool do_atomic_update ATTRIBUTE_UNUSED) {
   MarkObject(ref->AsMirrorPtr(), nullptr, MemberOffset(0));
 }
 
diff --git a/runtime/gc/collector/mark_sweep.h b/runtime/gc/collector/mark_sweep.h
index a6e2d61..5a9b9f8 100644
--- a/runtime/gc/collector/mark_sweep.h
+++ b/runtime/gc/collector/mark_sweep.h
@@ -216,7 +216,8 @@
       REQUIRES(!mark_stack_lock_)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
-  virtual void MarkHeapReference(mirror::HeapReference<mirror::Object>* ref) OVERRIDE
+  virtual void MarkHeapReference(mirror::HeapReference<mirror::Object>* ref,
+                                 bool do_atomic_update) OVERRIDE
       REQUIRES(Locks::heap_bitmap_lock_)
       REQUIRES(!mark_stack_lock_)
       REQUIRES_SHARED(Locks::mutator_lock_);
diff --git a/runtime/gc/collector/semi_space.cc b/runtime/gc/collector/semi_space.cc
index cb9e7e2..4c0f317 100644
--- a/runtime/gc/collector/semi_space.cc
+++ b/runtime/gc/collector/semi_space.cc
@@ -606,7 +606,8 @@
   return ref.AsMirrorPtr();
 }
 
-void SemiSpace::MarkHeapReference(mirror::HeapReference<mirror::Object>* obj_ptr) {
+void SemiSpace::MarkHeapReference(mirror::HeapReference<mirror::Object>* obj_ptr,
+                                  bool do_atomic_update ATTRIBUTE_UNUSED) {
   MarkObject(obj_ptr);
 }
 
diff --git a/runtime/gc/collector/semi_space.h b/runtime/gc/collector/semi_space.h
index 52b5e5f..9d6e74d 100644
--- a/runtime/gc/collector/semi_space.h
+++ b/runtime/gc/collector/semi_space.h
@@ -110,7 +110,8 @@
   virtual mirror::Object* MarkObject(mirror::Object* root) OVERRIDE
       REQUIRES(Locks::heap_bitmap_lock_, Locks::mutator_lock_);
 
-  virtual void MarkHeapReference(mirror::HeapReference<mirror::Object>* obj_ptr) OVERRIDE
+  virtual void MarkHeapReference(mirror::HeapReference<mirror::Object>* obj_ptr,
+                                 bool do_atomic_update) OVERRIDE
       REQUIRES(Locks::heap_bitmap_lock_, Locks::mutator_lock_);
 
   void ScanObject(mirror::Object* obj)
diff --git a/runtime/gc/heap.cc b/runtime/gc/heap.cc
index 34afa2a..53be30e 100644
--- a/runtime/gc/heap.cc
+++ b/runtime/gc/heap.cc
@@ -3332,7 +3332,7 @@
   virtual mirror::Object* MarkObject(mirror::Object* obj) OVERRIDE {
     return obj;
   }
-  virtual void MarkHeapReference(mirror::HeapReference<mirror::Object>*) OVERRIDE {
+  virtual void MarkHeapReference(mirror::HeapReference<mirror::Object>*, bool) OVERRIDE {
   }
 };
 
diff --git a/runtime/gc/reference_processor.cc b/runtime/gc/reference_processor.cc
index 86b1522..65a550e 100644
--- a/runtime/gc/reference_processor.cc
+++ b/runtime/gc/reference_processor.cc
@@ -139,6 +139,14 @@
       CHECK_EQ(!self->GetWeakRefAccessEnabled(), concurrent);
     }
   }
+  if (kIsDebugBuild && collector->IsTransactionActive()) {
+    // In transaction mode, we shouldn't enqueue any Reference to the queues.
+    // See DelayReferenceReferent().
+    DCHECK(soft_reference_queue_.IsEmpty());
+    DCHECK(weak_reference_queue_.IsEmpty());
+    DCHECK(finalizer_reference_queue_.IsEmpty());
+    DCHECK(phantom_reference_queue_.IsEmpty());
+  }
   // Unless required to clear soft references with white references, preserve some white referents.
   if (!clear_soft_references) {
     TimingLogger::ScopedTiming split(concurrent ? "ForwardSoftReferences" :
@@ -206,6 +214,15 @@
   // do_atomic_update needs to be true because this happens outside of the reference processing
   // phase.
   if (!collector->IsNullOrMarkedHeapReference(referent, /*do_atomic_update*/true)) {
+    if (UNLIKELY(collector->IsTransactionActive())) {
+      // In transaction mode, keep the referent alive and avoid any reference processing to avoid the
+      // issue of rolling back reference processing.  do_atomic_update needs to be true because this
+      // happens outside of the reference processing phase.
+      if (!referent->IsNull()) {
+        collector->MarkHeapReference(referent, /*do_atomic_update*/ true);
+      }
+      return;
+    }
     Thread* self = Thread::Current();
     // TODO: Remove these locks, and use atomic stacks for storing references?
     // We need to check that the references haven't already been enqueued since we can end up
diff --git a/runtime/gc/reference_queue.cc b/runtime/gc/reference_queue.cc
index 734caea..fd5dcf9 100644
--- a/runtime/gc/reference_queue.cc
+++ b/runtime/gc/reference_queue.cc
@@ -67,6 +67,11 @@
     list_->SetPendingNext(next);
   }
   ref->SetPendingNext(nullptr);
+  return ref;
+}
+
+// This must be called whenever DequeuePendingReference is called.
+void ReferenceQueue::DisableReadBarrierForReference(ObjPtr<mirror::Reference> ref) {
   Heap* heap = Runtime::Current()->GetHeap();
   if (kUseBakerOrBrooksReadBarrier && heap->CurrentCollectorType() == kCollectorTypeCC &&
       heap->ConcurrentCopyingCollector()->IsActive()) {
@@ -92,7 +97,6 @@
       }
     }
   }
-  return ref;
 }
 
 void ReferenceQueue::Dump(std::ostream& os) const {
@@ -140,6 +144,9 @@
       }
       cleared_references->EnqueueReference(ref);
     }
+    // Delay disabling the read barrier until here so that the ClearReferent call above in
+    // transaction mode will trigger the read barrier.
+    DisableReadBarrierForReference(ref);
   }
 }
 
@@ -162,6 +169,9 @@
       }
       cleared_references->EnqueueReference(ref);
     }
+    // Delay disabling the read barrier until here so that the ClearReferent call above in
+    // transaction mode will trigger the read barrier.
+    DisableReadBarrierForReference(ref->AsReference());
   }
 }
 
@@ -174,7 +184,9 @@
   do {
     mirror::HeapReference<mirror::Object>* referent_addr = ref->GetReferentReferenceAddr();
     if (referent_addr->AsMirrorPtr() != nullptr) {
-      visitor->MarkHeapReference(referent_addr);
+      // do_atomic_update is false because mutators can't access the referent due to the weak ref
+      // access blocking.
+      visitor->MarkHeapReference(referent_addr, /*do_atomic_update*/ false);
     }
     ref = ref->GetPendingNext();
   } while (LIKELY(ref != head));
diff --git a/runtime/gc/reference_queue.h b/runtime/gc/reference_queue.h
index b5ec1e5..b73a880 100644
--- a/runtime/gc/reference_queue.h
+++ b/runtime/gc/reference_queue.h
@@ -63,8 +63,15 @@
   void EnqueueReference(ObjPtr<mirror::Reference> ref) REQUIRES_SHARED(Locks::mutator_lock_);
 
   // Dequeue a reference from the queue and return that dequeued reference.
+  // Call DisableReadBarrierForReference for the reference that's returned from this function.
   ObjPtr<mirror::Reference> DequeuePendingReference() REQUIRES_SHARED(Locks::mutator_lock_);
 
+  // If applicable, disable the read barrier for the reference after its referent is handled (see
+  // ConcurrentCopying::ProcessMarkStackRef.) This must be called for a reference that's dequeued
+  // from pending queue (DequeuePendingReference).
+  void DisableReadBarrierForReference(ObjPtr<mirror::Reference> ref)
+      REQUIRES_SHARED(Locks::mutator_lock_);
+
   // Enqueues finalizer references with white referents.  White referents are blackened, moved to
   // the zombie field, and the referent field is cleared.
   void EnqueueFinalizerReferences(ReferenceQueue* cleared_references,
diff --git a/runtime/gc/space/image_space.cc b/runtime/gc/space/image_space.cc
index 2163a20..010ef11 100644
--- a/runtime/gc/space/image_space.cc
+++ b/runtime/gc/space/image_space.cc
@@ -1237,9 +1237,9 @@
           }
           dex_cache->FixupStrings<kWithoutReadBarrier>(new_strings, fixup_adapter);
         }
-        GcRoot<mirror::Class>* types = dex_cache->GetResolvedTypes();
+        mirror::TypeDexCacheType* types = dex_cache->GetResolvedTypes();
         if (types != nullptr) {
-          GcRoot<mirror::Class>* new_types = fixup_adapter.ForwardObject(types);
+          mirror::TypeDexCacheType* new_types = fixup_adapter.ForwardObject(types);
           if (types != new_types) {
             dex_cache->SetResolvedTypes(new_types);
           }
diff --git a/runtime/gc/space/region_space.h b/runtime/gc/space/region_space.h
index f3b9595..feab9b0 100644
--- a/runtime/gc/space/region_space.h
+++ b/runtime/gc/space/region_space.h
@@ -176,6 +176,14 @@
     return false;
   }
 
+  bool IsInNewlyAllocatedRegion(mirror::Object* ref) {
+    if (HasAddress(ref)) {
+      Region* r = RefToRegionUnlocked(ref);
+      return r->IsNewlyAllocated();
+    }
+    return false;
+  }
+
   bool IsInUnevacFromSpace(mirror::Object* ref) {
     if (HasAddress(ref)) {
       Region* r = RefToRegionUnlocked(ref);
@@ -351,6 +359,10 @@
       return idx_;
     }
 
+    bool IsNewlyAllocated() const {
+      return is_newly_allocated_;
+    }
+
     bool IsInFromSpace() const {
       return type_ == RegionType::kRegionTypeFromSpace;
     }
diff --git a/runtime/gc_root-inl.h b/runtime/gc_root-inl.h
index 390ed3c..7795c66 100644
--- a/runtime/gc_root-inl.h
+++ b/runtime/gc_root-inl.h
@@ -38,7 +38,7 @@
     : root_(mirror::CompressedReference<mirror::Object>::FromMirrorPtr(ref)) { }
 
 template<class MirrorType>
-inline GcRoot<MirrorType>::GcRoot(ObjPtr<MirrorType, kIsDebugBuild> ref)
+inline GcRoot<MirrorType>::GcRoot(ObjPtr<MirrorType> ref)
     : GcRoot(ref.Ptr()) { }
 
 inline std::string RootInfo::ToString() const {
diff --git a/runtime/gc_root.h b/runtime/gc_root.h
index 79e80f1..0894e9b 100644
--- a/runtime/gc_root.h
+++ b/runtime/gc_root.h
@@ -24,7 +24,7 @@
 namespace art {
 class ArtField;
 class ArtMethod;
-template<class MirrorType, bool kPoison> class ObjPtr;
+template<class MirrorType> class ObjPtr;
 
 namespace mirror {
 class Object;
@@ -215,7 +215,7 @@
   ALWAYS_INLINE GcRoot() {}
   explicit ALWAYS_INLINE GcRoot(MirrorType* ref)
       REQUIRES_SHARED(Locks::mutator_lock_);
-  explicit ALWAYS_INLINE GcRoot(ObjPtr<MirrorType, kIsDebugBuild> ref)
+  explicit ALWAYS_INLINE GcRoot(ObjPtr<MirrorType> ref)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
  private:
diff --git a/runtime/handle_scope-inl.h b/runtime/handle_scope-inl.h
index 077f45e..492d4b4 100644
--- a/runtime/handle_scope-inl.h
+++ b/runtime/handle_scope-inl.h
@@ -114,9 +114,9 @@
   return h;
 }
 
-template<size_t kNumReferences> template<class MirrorType, bool kPoison>
+template<size_t kNumReferences> template<class MirrorType>
 inline MutableHandle<MirrorType> FixedSizeHandleScope<kNumReferences>::NewHandle(
-    ObjPtr<MirrorType, kPoison> object) {
+    ObjPtr<MirrorType> object) {
   return NewHandle(object.Ptr());
 }
 
@@ -191,9 +191,8 @@
   return current_scope_->NewHandle(object);
 }
 
-template<class MirrorType, bool kPoison>
-inline MutableHandle<MirrorType> VariableSizedHandleScope::NewHandle(
-    ObjPtr<MirrorType, kPoison> ptr) {
+template<class MirrorType>
+inline MutableHandle<MirrorType> VariableSizedHandleScope::NewHandle(ObjPtr<MirrorType> ptr) {
   return NewHandle(ptr.Ptr());
 }
 
diff --git a/runtime/handle_scope.h b/runtime/handle_scope.h
index adb7d8a..c43a482 100644
--- a/runtime/handle_scope.h
+++ b/runtime/handle_scope.h
@@ -30,7 +30,7 @@
 namespace art {
 
 class HandleScope;
-template<class MirrorType, bool kPoison> class ObjPtr;
+template<class MirrorType> class ObjPtr;
 class Thread;
 class VariableSizedHandleScope;
 
@@ -224,8 +224,8 @@
   ALWAYS_INLINE HandleWrapperObjPtr<T> NewHandleWrapper(ObjPtr<T>* object)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
-  template<class MirrorType, bool kPoison>
-  ALWAYS_INLINE MutableHandle<MirrorType> NewHandle(ObjPtr<MirrorType, kPoison> object)
+  template<class MirrorType>
+  ALWAYS_INLINE MutableHandle<MirrorType> NewHandle(ObjPtr<MirrorType> object)
     REQUIRES_SHARED(Locks::mutator_lock_);
 
   ALWAYS_INLINE void SetReference(size_t i, mirror::Object* object)
@@ -250,7 +250,7 @@
   StackReference<mirror::Object> storage_[kNumReferences];
 
   // Position new handles will be created.
-  size_t pos_ = 0;
+  uint32_t pos_ = 0;
 
   template<size_t kNumRefs> friend class StackHandleScope;
   friend class VariableSizedHandleScope;
@@ -286,8 +286,8 @@
   template<class T>
   MutableHandle<T> NewHandle(T* object) REQUIRES_SHARED(Locks::mutator_lock_);
 
-  template<class MirrorType, bool kPoison>
-  MutableHandle<MirrorType> NewHandle(ObjPtr<MirrorType, kPoison> ptr)
+  template<class MirrorType>
+  MutableHandle<MirrorType> NewHandle(ObjPtr<MirrorType> ptr)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
   // Number of references contained within this handle scope.
@@ -299,12 +299,20 @@
   void VisitRoots(Visitor& visitor) REQUIRES_SHARED(Locks::mutator_lock_);
 
  private:
-  static constexpr size_t kNumReferencesPerScope = 4;
+  static constexpr size_t kLocalScopeSize = 64u;
+  static constexpr size_t kSizeOfReferencesPerScope =
+      kLocalScopeSize
+          - /* BaseHandleScope::link_ */ sizeof(BaseHandleScope*)
+          - /* BaseHandleScope::number_of_references_ */ sizeof(int32_t)
+          - /* FixedSizeHandleScope<>::pos_ */ sizeof(uint32_t);
+  static constexpr size_t kNumReferencesPerScope =
+      kSizeOfReferencesPerScope / sizeof(StackReference<mirror::Object>);
 
   Thread* const self_;
 
   // Linked list of fixed size handle scopes.
   using LocalScopeType = FixedSizeHandleScope<kNumReferencesPerScope>;
+  static_assert(sizeof(LocalScopeType) == kLocalScopeSize, "Unexpected size of LocalScopeType");
   LocalScopeType* current_scope_;
 
   DISALLOW_COPY_AND_ASSIGN(VariableSizedHandleScope);
diff --git a/runtime/image.cc b/runtime/image.cc
index 54b099e..4e6da79 100644
--- a/runtime/image.cc
+++ b/runtime/image.cc
@@ -25,7 +25,7 @@
 namespace art {
 
 const uint8_t ImageHeader::kImageMagic[] = { 'a', 'r', 't', '\n' };
-const uint8_t ImageHeader::kImageVersion[] = { '0', '3', '6', '\0' };  // Erroneous resolved class.
+const uint8_t ImageHeader::kImageVersion[] = { '0', '3', '8', '\0' };  // hash-based DexCache types
 
 ImageHeader::ImageHeader(uint32_t image_begin,
                          uint32_t image_size,
diff --git a/runtime/interpreter/unstarted_runtime.cc b/runtime/interpreter/unstarted_runtime.cc
index af0478c..80554c2 100644
--- a/runtime/interpreter/unstarted_runtime.cc
+++ b/runtime/interpreter/unstarted_runtime.cc
@@ -1330,17 +1330,18 @@
   result->SetC(string->CharAt(index));
 }
 
-// This allows setting chars from the new style of String objects during compilation.
-void UnstartedRuntime::UnstartedStringSetCharAt(
-    Thread* self, ShadowFrame* shadow_frame, JValue* result ATTRIBUTE_UNUSED, size_t arg_offset) {
-  jint index = shadow_frame->GetVReg(arg_offset + 1);
-  jchar c = shadow_frame->GetVReg(arg_offset + 2);
-  mirror::String* string = shadow_frame->GetVRegReference(arg_offset)->AsString();
+// This allows creating String objects with replaced characters during compilation.
+// String.doReplace(char, char) is called from String.replace(char, char) when there is a match.
+void UnstartedRuntime::UnstartedStringDoReplace(
+    Thread* self, ShadowFrame* shadow_frame, JValue* result, size_t arg_offset) {
+  jchar old_c = shadow_frame->GetVReg(arg_offset + 1);
+  jchar new_c = shadow_frame->GetVReg(arg_offset + 2);
+  ObjPtr<mirror::String> string = shadow_frame->GetVRegReference(arg_offset)->AsString();
   if (string == nullptr) {
-    AbortTransactionOrFail(self, "String.setCharAt with null object");
+    AbortTransactionOrFail(self, "String.replaceWithMatch with null object");
     return;
   }
-  string->SetCharAt(index, c);
+  result->SetL(string->DoReplace(self, old_c, new_c));
 }
 
 // This allows creating the new style of String objects during compilation.
diff --git a/runtime/interpreter/unstarted_runtime_list.h b/runtime/interpreter/unstarted_runtime_list.h
index 6fc7989..e9435e4 100644
--- a/runtime/interpreter/unstarted_runtime_list.h
+++ b/runtime/interpreter/unstarted_runtime_list.h
@@ -63,7 +63,7 @@
   V(RuntimeAvailableProcessors, "int java.lang.Runtime.availableProcessors()") \
   V(StringGetCharsNoCheck, "void java.lang.String.getCharsNoCheck(int, int, char[], int)") \
   V(StringCharAt, "char java.lang.String.charAt(int)") \
-  V(StringSetCharAt, "void java.lang.String.setCharAt(int, char)") \
+  V(StringDoReplace, "java.lang.String java.lang.String.doReplace(char, char)") \
   V(StringFactoryNewStringFromChars, "java.lang.String java.lang.StringFactory.newStringFromChars(int, int, char[])") \
   V(StringFactoryNewStringFromString, "java.lang.String java.lang.StringFactory.newStringFromString(java.lang.String)") \
   V(StringFastSubstring, "java.lang.String java.lang.String.fastSubstring(int, int)") \
diff --git a/runtime/jdwp/jdwp.h b/runtime/jdwp/jdwp.h
index 86af6d4..af29468 100644
--- a/runtime/jdwp/jdwp.h
+++ b/runtime/jdwp/jdwp.h
@@ -203,7 +203,8 @@
    */
   void PostLocationEvent(const EventLocation* pLoc, mirror::Object* thisPtr, int eventFlags,
                          const JValue* returnValue)
-     REQUIRES(!event_list_lock_, !jdwp_token_lock_) REQUIRES_SHARED(Locks::mutator_lock_);
+      REQUIRES(!Locks::jdwp_event_list_lock_, !jdwp_token_lock_)
+      REQUIRES_SHARED(Locks::mutator_lock_);
 
   /*
    * A field of interest has been accessed or modified. This is used for field access and field
@@ -214,7 +215,8 @@
    */
   void PostFieldEvent(const EventLocation* pLoc, ArtField* field, mirror::Object* thisPtr,
                       const JValue* fieldValue, bool is_modification)
-      REQUIRES(!event_list_lock_, !jdwp_token_lock_) REQUIRES_SHARED(Locks::mutator_lock_);
+      REQUIRES(!Locks::jdwp_event_list_lock_, !jdwp_token_lock_)
+      REQUIRES_SHARED(Locks::mutator_lock_);
 
   /*
    * An exception has been thrown.
@@ -223,19 +225,22 @@
    */
   void PostException(const EventLocation* pThrowLoc, mirror::Throwable* exception_object,
                      const EventLocation* pCatchLoc, mirror::Object* thisPtr)
-      REQUIRES(!event_list_lock_, !jdwp_token_lock_) REQUIRES_SHARED(Locks::mutator_lock_);
+      REQUIRES(!Locks::jdwp_event_list_lock_, !jdwp_token_lock_)
+      REQUIRES_SHARED(Locks::mutator_lock_);
 
   /*
    * A thread has started or stopped.
    */
   void PostThreadChange(Thread* thread, bool start)
-      REQUIRES(!event_list_lock_, !jdwp_token_lock_) REQUIRES_SHARED(Locks::mutator_lock_);
+      REQUIRES(!Locks::jdwp_event_list_lock_, !jdwp_token_lock_)
+      REQUIRES_SHARED(Locks::mutator_lock_);
 
   /*
    * Class has been prepared.
    */
   void PostClassPrepare(mirror::Class* klass)
-      REQUIRES(!event_list_lock_, !jdwp_token_lock_) REQUIRES_SHARED(Locks::mutator_lock_);
+      REQUIRES(!Locks::jdwp_event_list_lock_, !jdwp_token_lock_)
+      REQUIRES_SHARED(Locks::mutator_lock_);
 
   /*
    * The VM is about to stop.
@@ -259,7 +264,7 @@
   void SendRequest(ExpandBuf* pReq);
 
   void ResetState()
-      REQUIRES(!event_list_lock_)
+      REQUIRES(!Locks::jdwp_event_list_lock_)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
   /* atomic ops to get next serial number */
@@ -268,7 +273,7 @@
 
   void Run()
       REQUIRES(!Locks::mutator_lock_, !Locks::thread_suspend_count_lock_, !thread_start_lock_,
-               !attach_lock_, !event_list_lock_);
+               !attach_lock_, !Locks::jdwp_event_list_lock_);
 
   /*
    * Register an event by adding it to the event list.
@@ -277,25 +282,25 @@
    * may discard its pointer after calling this.
    */
   JdwpError RegisterEvent(JdwpEvent* pEvent)
-      REQUIRES(!event_list_lock_)
+      REQUIRES(!Locks::jdwp_event_list_lock_)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
   /*
    * Unregister an event, given the requestId.
    */
   void UnregisterEventById(uint32_t requestId)
-      REQUIRES(!event_list_lock_)
+      REQUIRES(!Locks::jdwp_event_list_lock_)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
   void UnregisterLocationEventsOnClass(ObjPtr<mirror::Class> klass)
-      REQUIRES(!event_list_lock_)
+      REQUIRES(!Locks::jdwp_event_list_lock_)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
   /*
    * Unregister all events.
    */
   void UnregisterAll()
-      REQUIRES(!event_list_lock_)
+      REQUIRES(!Locks::jdwp_event_list_lock_)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
  private:
@@ -310,16 +315,16 @@
                                      ObjectId threadId)
       REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!jdwp_token_lock_);
   void CleanupMatchList(const std::vector<JdwpEvent*>& match_list)
-      REQUIRES(event_list_lock_) REQUIRES_SHARED(Locks::mutator_lock_);
+      REQUIRES(Locks::jdwp_event_list_lock_) REQUIRES_SHARED(Locks::mutator_lock_);
   void EventFinish(ExpandBuf* pReq);
   bool FindMatchingEvents(JdwpEventKind eventKind, const ModBasket& basket,
                           std::vector<JdwpEvent*>* match_list)
-      REQUIRES(!event_list_lock_) REQUIRES_SHARED(Locks::mutator_lock_);
+      REQUIRES(!Locks::jdwp_event_list_lock_) REQUIRES_SHARED(Locks::mutator_lock_);
   void FindMatchingEventsLocked(JdwpEventKind eventKind, const ModBasket& basket,
                                 std::vector<JdwpEvent*>* match_list)
-      REQUIRES(event_list_lock_) REQUIRES_SHARED(Locks::mutator_lock_);
+      REQUIRES(Locks::jdwp_event_list_lock_) REQUIRES_SHARED(Locks::mutator_lock_);
   void UnregisterEvent(JdwpEvent* pEvent)
-      REQUIRES(event_list_lock_) REQUIRES_SHARED(Locks::mutator_lock_);
+      REQUIRES(Locks::jdwp_event_list_lock_) REQUIRES_SHARED(Locks::mutator_lock_);
   void SendBufferedRequest(uint32_t type, const std::vector<iovec>& iov);
 
   /*
@@ -387,9 +392,8 @@
   AtomicInteger event_serial_;
 
   // Linked list of events requested by the debugger (breakpoints, class prep, etc).
-  Mutex event_list_lock_ DEFAULT_MUTEX_ACQUIRED_AFTER ACQUIRED_BEFORE(Locks::breakpoint_lock_);
-  JdwpEvent* event_list_ GUARDED_BY(event_list_lock_);
-  size_t event_list_size_ GUARDED_BY(event_list_lock_);  // Number of elements in event_list_.
+  JdwpEvent* event_list_ GUARDED_BY(Locks::jdwp_event_list_lock_);
+  size_t event_list_size_ GUARDED_BY(Locks::jdwp_event_list_lock_);  // Number of elements in event_list_.
 
   // Used to synchronize JDWP command handler thread and event threads so only one
   // thread does JDWP stuff at a time. This prevent from interleaving command handling
@@ -410,7 +414,7 @@
   // When the runtime shuts down, it needs to stop JDWP command handler thread by closing the
   // JDWP connection. However, if the JDWP thread is processing a command, it needs to wait
   // for the command to finish so we can send its reply before closing the connection.
-  Mutex shutdown_lock_ ACQUIRED_AFTER(event_list_lock_);
+  Mutex shutdown_lock_ ACQUIRED_AFTER(Locks::jdwp_event_list_lock_);
   ConditionVariable shutdown_cond_ GUARDED_BY(shutdown_lock_);
   bool processing_request_ GUARDED_BY(shutdown_lock_);
 };
diff --git a/runtime/jdwp/jdwp_event.cc b/runtime/jdwp/jdwp_event.cc
index 96249f9..36d733e 100644
--- a/runtime/jdwp/jdwp_event.cc
+++ b/runtime/jdwp/jdwp_event.cc
@@ -237,7 +237,7 @@
     /*
      * Add to list.
      */
-    MutexLock mu(Thread::Current(), event_list_lock_);
+    MutexLock mu(Thread::Current(), *Locks::jdwp_event_list_lock_);
     if (event_list_ != nullptr) {
       pEvent->next = event_list_;
       event_list_->prev = pEvent;
@@ -256,7 +256,7 @@
   StackHandleScope<1> hs(Thread::Current());
   Handle<mirror::Class> h_klass(hs.NewHandle(klass));
   std::vector<JdwpEvent*> to_remove;
-  MutexLock mu(Thread::Current(), event_list_lock_);
+  MutexLock mu(Thread::Current(), *Locks::jdwp_event_list_lock_);
   for (JdwpEvent* cur_event = event_list_; cur_event != nullptr; cur_event = cur_event->next) {
     // Fill in the to_remove list
     bool found_event = false;
@@ -356,7 +356,7 @@
 void JdwpState::UnregisterEventById(uint32_t requestId) {
   bool found = false;
   {
-    MutexLock mu(Thread::Current(), event_list_lock_);
+    MutexLock mu(Thread::Current(), *Locks::jdwp_event_list_lock_);
 
     for (JdwpEvent* pEvent = event_list_; pEvent != nullptr; pEvent = pEvent->next) {
       if (pEvent->requestId == requestId) {
@@ -383,7 +383,7 @@
  * Remove all entries from the event list.
  */
 void JdwpState::UnregisterAll() {
-  MutexLock mu(Thread::Current(), event_list_lock_);
+  MutexLock mu(Thread::Current(), *Locks::jdwp_event_list_lock_);
 
   JdwpEvent* pEvent = event_list_;
   while (pEvent != nullptr) {
@@ -593,7 +593,7 @@
  */
 bool JdwpState::FindMatchingEvents(JdwpEventKind event_kind, const ModBasket& basket,
                                    std::vector<JdwpEvent*>* match_list) {
-  MutexLock mu(Thread::Current(), event_list_lock_);
+  MutexLock mu(Thread::Current(), *Locks::jdwp_event_list_lock_);
   match_list->reserve(event_list_size_);
   FindMatchingEventsLocked(event_kind, basket, match_list);
   return !match_list->empty();
@@ -908,7 +908,7 @@
   std::vector<JdwpEvent*> match_list;
   {
     // We use the locked version because we have multiple possible match events.
-    MutexLock mu(Thread::Current(), event_list_lock_);
+    MutexLock mu(Thread::Current(), *Locks::jdwp_event_list_lock_);
     match_list.reserve(event_list_size_);
     if ((eventFlags & Dbg::kBreakpoint) != 0) {
       FindMatchingEventsLocked(EK_BREAKPOINT, basket, &match_list);
@@ -955,7 +955,7 @@
   }
 
   {
-    MutexLock mu(Thread::Current(), event_list_lock_);
+    MutexLock mu(Thread::Current(), *Locks::jdwp_event_list_lock_);
     CleanupMatchList(match_list);
   }
 
@@ -1041,7 +1041,7 @@
   }
 
   {
-    MutexLock mu(Thread::Current(), event_list_lock_);
+    MutexLock mu(Thread::Current(), *Locks::jdwp_event_list_lock_);
     CleanupMatchList(match_list);
   }
 
@@ -1103,7 +1103,7 @@
   }
 
   {
-    MutexLock mu(Thread::Current(), event_list_lock_);
+    MutexLock mu(Thread::Current(), *Locks::jdwp_event_list_lock_);
     CleanupMatchList(match_list);
   }
 
@@ -1213,7 +1213,7 @@
   }
 
   {
-    MutexLock mu(Thread::Current(), event_list_lock_);
+    MutexLock mu(Thread::Current(), *Locks::jdwp_event_list_lock_);
     CleanupMatchList(match_list);
   }
 
@@ -1295,7 +1295,7 @@
   }
 
   {
-    MutexLock mu(Thread::Current(), event_list_lock_);
+    MutexLock mu(Thread::Current(), *Locks::jdwp_event_list_lock_);
     CleanupMatchList(match_list);
   }
 
diff --git a/runtime/jdwp/jdwp_main.cc b/runtime/jdwp/jdwp_main.cc
index 7707ba4..64ed724 100644
--- a/runtime/jdwp/jdwp_main.cc
+++ b/runtime/jdwp/jdwp_main.cc
@@ -227,7 +227,6 @@
       last_activity_time_ms_(0),
       request_serial_(0x10000000),
       event_serial_(0x20000000),
-      event_list_lock_("JDWP event list lock", kJdwpEventListLock),
       event_list_(nullptr),
       event_list_size_(0),
       jdwp_token_lock_("JDWP token lock"),
@@ -331,7 +330,7 @@
 
   UnregisterAll();
   {
-    MutexLock mu(Thread::Current(), event_list_lock_);
+    MutexLock mu(Thread::Current(), *Locks::jdwp_event_list_lock_);
     CHECK(event_list_ == nullptr);
   }
 
diff --git a/runtime/jit/jit_code_cache.cc b/runtime/jit/jit_code_cache.cc
index 60ab275..b1ba952 100644
--- a/runtime/jit/jit_code_cache.cc
+++ b/runtime/jit/jit_code_cache.cc
@@ -1245,15 +1245,55 @@
 }
 
 void JitCodeCache::GetProfiledMethods(const std::set<std::string>& dex_base_locations,
-                                      std::vector<MethodReference>& methods) {
+                                      std::vector<ProfileMethodInfo>& methods) {
   ScopedTrace trace(__FUNCTION__);
   MutexLock mu(Thread::Current(), lock_);
   for (const ProfilingInfo* info : profiling_infos_) {
     ArtMethod* method = info->GetMethod();
     const DexFile* dex_file = method->GetDexFile();
-    if (ContainsElement(dex_base_locations, dex_file->GetBaseLocation())) {
-      methods.emplace_back(dex_file,  method->GetDexMethodIndex());
+    if (!ContainsElement(dex_base_locations, dex_file->GetBaseLocation())) {
+      // Skip dex files which are not profiled.
+      continue;
     }
+    std::vector<ProfileMethodInfo::ProfileInlineCache> inline_caches;
+    for (size_t i = 0; i < info->number_of_inline_caches_; ++i) {
+      std::vector<ProfileMethodInfo::ProfileClassReference> profile_classes;
+      const InlineCache& cache = info->cache_[i];
+      for (size_t k = 0; k < InlineCache::kIndividualCacheSize; k++) {
+        mirror::Class* cls = cache.classes_[k].Read();
+        if (cls == nullptr) {
+          break;
+        }
+
+        const DexFile* class_dex_file = nullptr;
+        dex::TypeIndex type_index;
+
+        if (cls->GetDexCache() == nullptr) {
+          DCHECK(cls->IsArrayClass()) << cls->PrettyClass();
+          class_dex_file = dex_file;
+          type_index = cls->FindTypeIndexInOtherDexFile(*dex_file);
+        } else {
+          class_dex_file = &(cls->GetDexFile());
+          type_index = cls->GetDexTypeIndex();
+        }
+        if (!type_index.IsValid()) {
+          // Could be a proxy class or an array for which we couldn't find the type index.
+          // TODO(calin): can we really miss the type index for arrays here?
+          continue;
+        }
+        if (ContainsElement(dex_base_locations, class_dex_file->GetBaseLocation())) {
+          // Only consider classes from the same apk (including multidex).
+          profile_classes.emplace_back(/*ProfileMethodInfo::ProfileClassReference*/
+              class_dex_file, type_index);
+        }
+      }
+      if (!profile_classes.empty()) {
+        inline_caches.emplace_back(/*ProfileMethodInfo::ProfileInlineCache*/
+            cache.dex_pc_, profile_classes);
+      }
+    }
+    methods.emplace_back(/*ProfileMethodInfo*/
+        dex_file, method->GetDexMethodIndex(), inline_caches);
   }
 }
 
diff --git a/runtime/jit/jit_code_cache.h b/runtime/jit/jit_code_cache.h
index b5e3176..33a792f 100644
--- a/runtime/jit/jit_code_cache.h
+++ b/runtime/jit/jit_code_cache.h
@@ -30,6 +30,7 @@
 #include "method_reference.h"
 #include "oat_file.h"
 #include "object_callbacks.h"
+#include "profile_compilation_info.h"
 #include "safe_map.h"
 #include "thread_pool.h"
 
@@ -192,7 +193,7 @@
 
   // Adds to `methods` all profiled methods which are part of any of the given dex locations.
   void GetProfiledMethods(const std::set<std::string>& dex_base_locations,
-                          std::vector<MethodReference>& methods)
+                          std::vector<ProfileMethodInfo>& methods)
       REQUIRES(!lock_)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
diff --git a/runtime/jit/profile_compilation_info.cc b/runtime/jit/profile_compilation_info.cc
index 54fc038..627cc93 100644
--- a/runtime/jit/profile_compilation_info.cc
+++ b/runtime/jit/profile_compilation_info.cc
@@ -37,7 +37,7 @@
 namespace art {
 
 const uint8_t ProfileCompilationInfo::kProfileMagic[] = { 'p', 'r', 'o', '\0' };
-const uint8_t ProfileCompilationInfo::kProfileVersion[] = { '0', '0', '2', '\0' };
+const uint8_t ProfileCompilationInfo::kProfileVersion[] = { '0', '0', '3', '\0' };  // inline caches
 
 static constexpr uint16_t kMaxDexFileKeyLength = PATH_MAX;
 
@@ -46,6 +46,25 @@
 // using the same test profile.
 static constexpr bool kDebugIgnoreChecksum = false;
 
+static constexpr uint8_t kMegamorphicEncoding = 7;
+
+static_assert(sizeof(InlineCache::kIndividualCacheSize) == sizeof(uint8_t),
+              "InlineCache::kIndividualCacheSize does not have the expect type size");
+static_assert(InlineCache::kIndividualCacheSize < kMegamorphicEncoding,
+              "InlineCache::kIndividualCacheSize is larger than expected");
+
+void ProfileCompilationInfo::DexPcData::AddClass(uint16_t dex_profile_idx,
+                                                 const dex::TypeIndex& type_idx) {
+  if (is_megamorphic) {
+    return;
+  }
+  classes.emplace(dex_profile_idx, type_idx);
+  if (classes.size() >= InlineCache::kIndividualCacheSize) {
+    is_megamorphic = true;
+    classes.clear();
+  }
+}
+
 // Transform the actual dex location into relative paths.
 // Note: this is OK because we don't store profiles of different apps into the same file.
 // Apps with split apks don't cause trouble because each split has a different name and will not
@@ -62,12 +81,10 @@
 }
 
 bool ProfileCompilationInfo::AddMethodsAndClasses(
-    const std::vector<MethodReference>& methods,
+    const std::vector<ProfileMethodInfo>& methods,
     const std::set<DexCacheResolvedClasses>& resolved_classes) {
-  for (const MethodReference& method : methods) {
-    if (!AddMethodIndex(GetProfileDexFileKey(method.dex_file->GetLocation()),
-                        method.dex_file->GetLocationChecksum(),
-                        method.dex_method_index)) {
+  for (const ProfileMethodInfo& method : methods) {
+    if (!AddMethod(method)) {
       return false;
     }
   }
@@ -170,29 +187,40 @@
 }
 
 static constexpr size_t kLineHeaderSize =
-    3 * sizeof(uint16_t) +  // method_set.size + class_set.size + dex_location.size
-    sizeof(uint32_t);       // checksum
+    2 * sizeof(uint16_t) +  // class_set.size + dex_location.size
+    2 * sizeof(uint32_t);   // method_map.size + checksum
 
 /**
  * Serialization format:
- *    magic,version,number_of_lines
- *    dex_location1,number_of_methods1,number_of_classes1,dex_location_checksum1, \
- *        method_id11,method_id12...,class_id1,class_id2...
- *    dex_location2,number_of_methods2,number_of_classes2,dex_location_checksum2, \
- *        method_id21,method_id22...,,class_id1,class_id2...
+ *    magic,version,number_of_dex_files
+ *    dex_location1,number_of_classes1,methods_region_size,dex_location_checksum1, \
+ *        method_encoding_11,method_encoding_12...,class_id1,class_id2...
+ *    dex_location2,number_of_classes2,methods_region_size,dex_location_checksum2, \
+ *        method_encoding_21,method_encoding_22...,,class_id1,class_id2...
  *    .....
+ * The method_encoding is:
+ *    method_id,number_of_inline_caches,inline_cache1,inline_cache2...
+ * The inline_cache is:
+ *    dex_pc,[M|dex_map_size], dex_profile_index,class_id1,class_id2...,dex_profile_index2,...
+ *    dex_map_size is the number of dex_indeces that follows.
+ *       Classes are grouped per their dex files and the line
+ *       `dex_profile_index,class_id1,class_id2...,dex_profile_index2,...` encodes the
+ *       mapping from `dex_profile_index` to the set of classes `class_id1,class_id2...`
+ *    M stands for megamorphic and it's encoded as the byte kMegamorphicEncoding.
+ *    When present, there will be no class ids following.
  **/
 bool ProfileCompilationInfo::Save(int fd) {
   ScopedTrace trace(__PRETTY_FUNCTION__);
   DCHECK_GE(fd, 0);
 
-  // Cache at most 5KB before writing.
-  static constexpr size_t kMaxSizeToKeepBeforeWriting = 5 * KB;
+  // Cache at most 50KB before writing.
+  static constexpr size_t kMaxSizeToKeepBeforeWriting = 50 * KB;
   // Use a vector wrapper to avoid keeping track of offsets when we add elements.
   std::vector<uint8_t> buffer;
   WriteBuffer(fd, kProfileMagic, sizeof(kProfileMagic));
   WriteBuffer(fd, kProfileVersion, sizeof(kProfileVersion));
-  AddUintToBuffer(&buffer, static_cast<uint16_t>(info_.size()));
+  DCHECK_LE(info_.size(), std::numeric_limits<uint8_t>::max());
+  AddUintToBuffer(&buffer, static_cast<uint8_t>(info_.size()));
 
   for (const auto& it : info_) {
     if (buffer.size() > kMaxSizeToKeepBeforeWriting) {
@@ -203,9 +231,9 @@
     }
     const std::string& dex_location = it.first;
     const DexFileData& dex_data = it.second;
-    if (dex_data.method_set.empty() && dex_data.class_set.empty()) {
-      continue;
-    }
+
+    // Note that we allow dex files without any methods or classes, so that
+    // inline caches can refer valid dex files.
 
     if (dex_location.size() >= kMaxDexFileKeyLength) {
       LOG(WARNING) << "DexFileKey exceeds allocated limit";
@@ -214,42 +242,128 @@
 
     // Make sure that the buffer has enough capacity to avoid repeated resizings
     // while we add data.
+    uint32_t methods_region_size = GetMethodsRegionSize(dex_data);
     size_t required_capacity = buffer.size() +
         kLineHeaderSize +
         dex_location.size() +
-        sizeof(uint16_t) * (dex_data.class_set.size() + dex_data.method_set.size());
+        sizeof(uint16_t) * dex_data.class_set.size() +
+        methods_region_size;
 
     buffer.reserve(required_capacity);
-
     DCHECK_LE(dex_location.size(), std::numeric_limits<uint16_t>::max());
-    DCHECK_LE(dex_data.method_set.size(), std::numeric_limits<uint16_t>::max());
     DCHECK_LE(dex_data.class_set.size(), std::numeric_limits<uint16_t>::max());
     AddUintToBuffer(&buffer, static_cast<uint16_t>(dex_location.size()));
-    AddUintToBuffer(&buffer, static_cast<uint16_t>(dex_data.method_set.size()));
     AddUintToBuffer(&buffer, static_cast<uint16_t>(dex_data.class_set.size()));
+    AddUintToBuffer(&buffer, methods_region_size);  // uint32_t
     AddUintToBuffer(&buffer, dex_data.checksum);  // uint32_t
 
     AddStringToBuffer(&buffer, dex_location);
 
-    for (auto method_it : dex_data.method_set) {
-      AddUintToBuffer(&buffer, method_it);
+    for (const auto& method_it : dex_data.method_map) {
+      AddUintToBuffer(&buffer, method_it.first);
+      AddInlineCacheToBuffer(&buffer, method_it.second);
     }
-    for (auto class_id : dex_data.class_set) {
+    for (const auto& class_id : dex_data.class_set) {
       AddUintToBuffer(&buffer, class_id.index_);
     }
-    DCHECK_EQ(required_capacity, buffer.size())
+
+    DCHECK_LE(required_capacity, buffer.size())
         << "Failed to add the expected number of bytes in the buffer";
   }
 
   return WriteBuffer(fd, buffer.data(), buffer.size());
 }
 
+void ProfileCompilationInfo::AddInlineCacheToBuffer(std::vector<uint8_t>* buffer,
+                                                    const InlineCacheMap& inline_cache_map) {
+  // Add inline cache map size.
+  AddUintToBuffer(buffer, static_cast<uint16_t>(inline_cache_map.size()));
+  if (inline_cache_map.size() == 0) {
+    return;
+  }
+  for (const auto& inline_cache_it : inline_cache_map) {
+    uint16_t dex_pc = inline_cache_it.first;
+    const DexPcData dex_pc_data = inline_cache_it.second;
+    const ClassSet& classes = dex_pc_data.classes;
+
+    // Add the dex pc.
+    AddUintToBuffer(buffer, dex_pc);
+
+    if (dex_pc_data.is_megamorphic) {
+      // Add the megamorphic encoding if needed and continue.
+      // If megamorphic, we don't add the rest of the classes.
+      AddUintToBuffer(buffer, kMegamorphicEncoding);
+      continue;
+    }
+
+    DCHECK_LT(classes.size(), InlineCache::kIndividualCacheSize);
+    DCHECK_NE(classes.size(), 0u) << "InlineCache contains a dex_pc with 0 classes";
+
+    SafeMap<uint8_t, std::vector<dex::TypeIndex>> dex_to_classes_map;
+    // Group the classes by dex. We expect that most of the classes will come from
+    // the same dex, so this will be more efficient than encoding the dex index
+    // for each class reference.
+    GroupClassesByDex(classes, &dex_to_classes_map);
+    // Add the dex map size.
+    AddUintToBuffer(buffer, static_cast<uint8_t>(dex_to_classes_map.size()));
+    for (const auto& dex_it : dex_to_classes_map) {
+      uint8_t dex_profile_index = dex_it.first;
+      const std::vector<dex::TypeIndex>& dex_classes = dex_it.second;
+      // Add the dex profile index.
+      AddUintToBuffer(buffer, dex_profile_index);
+      // Add the the number of classes for each dex profile index.
+      AddUintToBuffer(buffer, static_cast<uint8_t>(dex_classes.size()));
+      for (size_t i = 0; i < dex_classes.size(); i++) {
+        // Add the type index of the classes.
+        AddUintToBuffer(buffer, dex_classes[i].index_);
+      }
+    }
+  }
+}
+
+uint32_t ProfileCompilationInfo::GetMethodsRegionSize(const DexFileData& dex_data) {
+  // ((uint16_t)method index + (uint16_t)inline cache size) * number of methods
+  uint32_t size = 2 * sizeof(uint16_t) * dex_data.method_map.size();
+  for (const auto& method_it : dex_data.method_map) {
+    const InlineCacheMap& inline_cache = method_it.second;
+    size += sizeof(uint16_t) * inline_cache.size();  // dex_pc
+    for (const auto& inline_cache_it : inline_cache) {
+      const ClassSet& classes = inline_cache_it.second.classes;
+      SafeMap<uint8_t, std::vector<dex::TypeIndex>> dex_to_classes_map;
+      GroupClassesByDex(classes, &dex_to_classes_map);
+      size += sizeof(uint8_t);  // dex_to_classes_map size
+      for (const auto& dex_it : dex_to_classes_map) {
+        size += sizeof(uint8_t);  // dex profile index
+        size += sizeof(uint8_t);  // number of classes
+        const std::vector<dex::TypeIndex>& dex_classes = dex_it.second;
+        size += sizeof(uint16_t) * dex_classes.size();  // the actual classes
+      }
+    }
+  }
+  return size;
+}
+
+void ProfileCompilationInfo::GroupClassesByDex(
+    const ClassSet& classes,
+    /*out*/SafeMap<uint8_t, std::vector<dex::TypeIndex>>* dex_to_classes_map) {
+  for (const auto& classes_it : classes) {
+    auto dex_it = dex_to_classes_map->FindOrAdd(classes_it.dex_profile_index);
+    dex_it->second.push_back(classes_it.type_index);
+  }
+}
+
 ProfileCompilationInfo::DexFileData* ProfileCompilationInfo::GetOrAddDexFileData(
     const std::string& dex_location,
     uint32_t checksum) {
-  auto info_it = info_.find(dex_location);
-  if (info_it == info_.end()) {
-    info_it = info_.Put(dex_location, DexFileData(checksum));
+  auto info_it = info_.FindOrAdd(dex_location, DexFileData(checksum, info_.size()));
+  if (info_.size() > std::numeric_limits<uint8_t>::max()) {
+    // Allow only 255 dex files to be profiled. This allows us to save bytes
+    // when encoding. The number is well above what we expect for normal applications.
+    if (kIsDebugBuild) {
+      LOG(WARNING) << "Exceeded the maximum number of dex files (255). Something went wrong";
+    }
+    info_.erase(dex_location);
+    return nullptr;
   }
   if (info_it->second.checksum != checksum) {
     LOG(WARNING) << "Checksum mismatch for dex " << dex_location;
@@ -270,13 +384,65 @@
 }
 
 bool ProfileCompilationInfo::AddMethodIndex(const std::string& dex_location,
-                                            uint32_t checksum,
-                                            uint16_t method_idx) {
-  DexFileData* const data = GetOrAddDexFileData(dex_location, checksum);
-  if (data == nullptr) {
+                                            uint32_t dex_checksum,
+                                            uint16_t method_index) {
+  return AddMethod(dex_location, dex_checksum, method_index, OfflineProfileMethodInfo());
+}
+
+bool ProfileCompilationInfo::AddMethod(const std::string& dex_location,
+                                       uint32_t dex_checksum,
+                                       uint16_t method_index,
+                                       const OfflineProfileMethodInfo& pmi) {
+  DexFileData* const data = GetOrAddDexFileData(
+      GetProfileDexFileKey(dex_location),
+      dex_checksum);
+  if (data == nullptr) {  // checksum mismatch
     return false;
   }
-  data->method_set.insert(method_idx);
+  auto inline_cache_it = data->method_map.FindOrAdd(method_index);
+  for (const auto& pmi_inline_cache_it : pmi.inline_caches) {
+    uint16_t pmi_ic_dex_pc = pmi_inline_cache_it.first;
+    const DexPcData& pmi_ic_dex_pc_data = pmi_inline_cache_it.second;
+    auto dex_pc_data_it = inline_cache_it->second.FindOrAdd(pmi_ic_dex_pc);
+    if (pmi_ic_dex_pc_data.is_megamorphic) {
+      dex_pc_data_it->second.SetMegamorphic();
+      continue;
+    }
+    for (const ClassReference& class_ref : pmi_ic_dex_pc_data.classes) {
+      const DexReference& dex_ref = pmi.dex_references[class_ref.dex_profile_index];
+      DexFileData* class_dex_data = GetOrAddDexFileData(
+          GetProfileDexFileKey(dex_ref.dex_location),
+          dex_ref.dex_checksum);
+      if (class_dex_data == nullptr) {  // checksum mismatch
+        return false;
+      }
+      dex_pc_data_it->second.AddClass(class_dex_data->profile_index, class_ref.type_index);
+    }
+  }
+  return true;
+}
+
+bool ProfileCompilationInfo::AddMethod(const ProfileMethodInfo& pmi) {
+  DexFileData* const data = GetOrAddDexFileData(
+      GetProfileDexFileKey(pmi.dex_file->GetLocation()),
+      pmi.dex_file->GetLocationChecksum());
+  if (data == nullptr) {  // checksum mismatch
+    return false;
+  }
+  auto inline_cache_it = data->method_map.FindOrAdd(pmi.dex_method_index);
+
+  for (const ProfileMethodInfo::ProfileInlineCache& cache : pmi.inline_caches) {
+    for (const ProfileMethodInfo::ProfileClassReference& class_ref : cache.classes) {
+      DexFileData* class_dex_data = GetOrAddDexFileData(
+          GetProfileDexFileKey(class_ref.dex_file->GetLocation()),
+          class_ref.dex_file->GetLocationChecksum());
+      if (class_dex_data == nullptr) {  // checksum mismatch
+        return false;
+      }
+      auto dex_pc_data_it = inline_cache_it->second.FindOrAdd(cache.dex_pc);
+      dex_pc_data_it->second.AddClass(class_dex_data->profile_index, class_ref.type_index);
+    }
+  }
   return true;
 }
 
@@ -291,21 +457,79 @@
   return true;
 }
 
-bool ProfileCompilationInfo::ProcessLine(SafeBuffer& line_buffer,
-                                         uint16_t method_set_size,
-                                         uint16_t class_set_size,
-                                         uint32_t checksum,
-                                         const std::string& dex_location) {
-  for (uint16_t i = 0; i < method_set_size; i++) {
-    uint16_t method_idx = line_buffer.ReadUintAndAdvance<uint16_t>();
-    if (!AddMethodIndex(dex_location, checksum, method_idx)) {
+#define READ_UINT(type, buffer, dest, error)          \
+  do {                                                \
+    if (!buffer.ReadUintAndAdvance<type>(&dest)) {    \
+      *error = "Could not read "#dest;                \
+      return false;                                   \
+    }                                                 \
+  }                                                   \
+  while (false)
+
+bool ProfileCompilationInfo::ReadInlineCache(SafeBuffer& buffer,
+                                             uint8_t number_of_dex_files,
+                                             /*out*/ InlineCacheMap* inline_cache,
+                                             /*out*/ std::string* error) {
+  uint16_t inline_cache_size;
+  READ_UINT(uint16_t, buffer, inline_cache_size, error);
+  for (; inline_cache_size > 0; inline_cache_size--) {
+    uint16_t dex_pc;
+    uint8_t dex_to_classes_map_size;
+    READ_UINT(uint16_t, buffer, dex_pc, error);
+    READ_UINT(uint8_t, buffer, dex_to_classes_map_size, error);
+    auto dex_pc_data_it = inline_cache->FindOrAdd(dex_pc);
+    if (dex_to_classes_map_size == kMegamorphicEncoding) {
+      dex_pc_data_it->second.SetMegamorphic();
+      continue;
+    }
+    for (; dex_to_classes_map_size > 0; dex_to_classes_map_size--) {
+      uint8_t dex_profile_index;
+      uint8_t dex_classes_size;
+      READ_UINT(uint8_t, buffer, dex_profile_index, error);
+      READ_UINT(uint8_t, buffer, dex_classes_size, error);
+      if (dex_profile_index >= number_of_dex_files) {
+        *error = "dex_profile_index out of bounds ";
+        *error += std::to_string(dex_profile_index) + " " + std::to_string(number_of_dex_files);
+        return false;
+      }
+      for (; dex_classes_size > 0; dex_classes_size--) {
+        uint16_t type_index;
+        READ_UINT(uint16_t, buffer, type_index, error);
+        dex_pc_data_it->second.AddClass(dex_profile_index, dex::TypeIndex(type_index));
+      }
+    }
+  }
+  return true;
+}
+
+bool ProfileCompilationInfo::ReadMethods(SafeBuffer& buffer,
+                                         uint8_t number_of_dex_files,
+                                         const ProfileLineHeader& line_header,
+                                         /*out*/std::string* error) {
+  while (buffer.HasMoreData()) {
+    DexFileData* const data = GetOrAddDexFileData(line_header.dex_location, line_header.checksum);
+    uint16_t method_index;
+    READ_UINT(uint16_t, buffer, method_index, error);
+
+    auto it = data->method_map.FindOrAdd(method_index);
+    if (!ReadInlineCache(buffer, number_of_dex_files, &(it->second), error)) {
       return false;
     }
   }
 
-  for (uint16_t i = 0; i < class_set_size; i++) {
-    uint16_t type_idx = line_buffer.ReadUintAndAdvance<uint16_t>();
-    if (!AddClassIndex(dex_location, checksum, dex::TypeIndex(type_idx))) {
+  return true;
+}
+
+bool ProfileCompilationInfo::ReadClasses(SafeBuffer& buffer,
+                                         uint16_t classes_to_read,
+                                         const ProfileLineHeader& line_header,
+                                         /*out*/std::string* error) {
+  for (uint16_t i = 0; i < classes_to_read; i++) {
+    uint16_t type_index;
+    READ_UINT(uint16_t, buffer, type_index, error);
+    if (!AddClassIndex(line_header.dex_location,
+                       line_header.checksum,
+                       dex::TypeIndex(type_index))) {
       return false;
     }
   }
@@ -324,15 +548,17 @@
 
 // Reads an uint value previously written with AddUintToBuffer.
 template <typename T>
-T ProfileCompilationInfo::SafeBuffer::ReadUintAndAdvance() {
+bool ProfileCompilationInfo::SafeBuffer::ReadUintAndAdvance(/*out*/T* value) {
   static_assert(std::is_unsigned<T>::value, "Type is not unsigned");
-  CHECK_LE(ptr_current_ + sizeof(T), ptr_end_);
-  T value = 0;
+  if (ptr_current_ + sizeof(T) > ptr_end_) {
+    return false;
+  }
+  *value = 0;
   for (size_t i = 0; i < sizeof(T); i++) {
-    value += ptr_current_[i] << (i * kBitsPerByte);
+    *value += ptr_current_[i] << (i * kBitsPerByte);
   }
   ptr_current_ += sizeof(T);
-  return value;
+  return true;
 }
 
 bool ProfileCompilationInfo::SafeBuffer::CompareAndAdvance(const uint8_t* data, size_t data_size) {
@@ -346,6 +572,10 @@
   return false;
 }
 
+bool ProfileCompilationInfo::SafeBuffer::HasMoreData() {
+  return ptr_current_ < ptr_end_;
+}
+
 ProfileCompilationInfo::ProfileLoadSatus ProfileCompilationInfo::SafeBuffer::FillFromFd(
       int fd,
       const std::string& source,
@@ -369,13 +599,13 @@
 
 ProfileCompilationInfo::ProfileLoadSatus ProfileCompilationInfo::ReadProfileHeader(
       int fd,
-      /*out*/uint16_t* number_of_lines,
+      /*out*/uint8_t* number_of_dex_files,
       /*out*/std::string* error) {
   // Read magic and version
   const size_t kMagicVersionSize =
     sizeof(kProfileMagic) +
     sizeof(kProfileVersion) +
-    sizeof(uint16_t);  // number of lines
+    sizeof(uint8_t);  // number of dex files
 
   SafeBuffer safe_buffer(kMagicVersionSize);
 
@@ -392,24 +622,38 @@
     *error = "Profile version mismatch";
     return kProfileLoadVersionMismatch;
   }
-  *number_of_lines = safe_buffer.ReadUintAndAdvance<uint16_t>();
+  if (!safe_buffer.ReadUintAndAdvance<uint8_t>(number_of_dex_files)) {
+    *error = "Cannot read the number of dex files";
+    return kProfileLoadBadData;
+  }
   return kProfileLoadSuccess;
 }
 
+bool ProfileCompilationInfo::ReadProfileLineHeaderElements(SafeBuffer& buffer,
+                                                           /*out*/uint16_t* dex_location_size,
+                                                           /*out*/ProfileLineHeader* line_header,
+                                                           /*out*/std::string* error) {
+  READ_UINT(uint16_t, buffer, *dex_location_size, error);
+  READ_UINT(uint16_t, buffer, line_header->class_set_size, error);
+  READ_UINT(uint32_t, buffer, line_header->method_region_size_bytes, error);
+  READ_UINT(uint32_t, buffer, line_header->checksum, error);
+  return true;
+}
+
 ProfileCompilationInfo::ProfileLoadSatus ProfileCompilationInfo::ReadProfileLineHeader(
       int fd,
       /*out*/ProfileLineHeader* line_header,
       /*out*/std::string* error) {
   SafeBuffer header_buffer(kLineHeaderSize);
-  ProfileLoadSatus status = header_buffer.FillFromFd(fd, "ReadProfileHeader", error);
+  ProfileLoadSatus status = header_buffer.FillFromFd(fd, "ReadProfileLineHeader", error);
   if (status != kProfileLoadSuccess) {
     return status;
   }
 
-  uint16_t dex_location_size = header_buffer.ReadUintAndAdvance<uint16_t>();
-  line_header->method_set_size = header_buffer.ReadUintAndAdvance<uint16_t>();
-  line_header->class_set_size = header_buffer.ReadUintAndAdvance<uint16_t>();
-  line_header->checksum = header_buffer.ReadUintAndAdvance<uint32_t>();
+  uint16_t dex_location_size;
+  if (!ReadProfileLineHeaderElements(header_buffer, &dex_location_size, line_header, error)) {
+    return kProfileLoadBadData;
+  }
 
   if (dex_location_size == 0 || dex_location_size > kMaxDexFileKeyLength) {
     *error = "DexFileKey has an invalid size: " +
@@ -429,37 +673,38 @@
 
 ProfileCompilationInfo::ProfileLoadSatus ProfileCompilationInfo::ReadProfileLine(
       int fd,
+      uint8_t number_of_dex_files,
       const ProfileLineHeader& line_header,
       /*out*/std::string* error) {
-  // Make sure that we don't try to read everything in memory (in case the profile if full).
-  // Split readings in chunks of at most 10kb.
-  static constexpr uint16_t kMaxNumberOfEntriesToRead = 5120;
-  uint16_t methods_left_to_read = line_header.method_set_size;
-  uint16_t classes_left_to_read = line_header.class_set_size;
+  if (GetOrAddDexFileData(line_header.dex_location, line_header.checksum) == nullptr) {
+    *error = "Error when reading profile file line header: checksum mismatch for "
+        + line_header.dex_location;
+    return kProfileLoadBadData;
+  }
 
-  while ((methods_left_to_read > 0) || (classes_left_to_read > 0)) {
-    uint16_t methods_to_read = std::min(kMaxNumberOfEntriesToRead, methods_left_to_read);
-    uint16_t max_classes_to_read = kMaxNumberOfEntriesToRead - methods_to_read;
-    uint16_t classes_to_read = std::min(max_classes_to_read, classes_left_to_read);
-
-    size_t line_size = sizeof(uint16_t) * (methods_to_read + classes_to_read);
-    SafeBuffer line_buffer(line_size);
-
-    ProfileLoadSatus status = line_buffer.FillFromFd(fd, "ReadProfileLine", error);
+  {
+    SafeBuffer buffer(line_header.method_region_size_bytes);
+    ProfileLoadSatus status = buffer.FillFromFd(fd, "ReadProfileLineMethods", error);
     if (status != kProfileLoadSuccess) {
       return status;
     }
-    if (!ProcessLine(line_buffer,
-                     methods_to_read,
-                     classes_to_read,
-                     line_header.checksum,
-                     line_header.dex_location)) {
-      *error = "Error when reading profile file line";
+
+    if (!ReadMethods(buffer, number_of_dex_files, line_header, error)) {
       return kProfileLoadBadData;
     }
-    methods_left_to_read -= methods_to_read;
-    classes_left_to_read -= classes_to_read;
   }
+
+  {
+    SafeBuffer buffer(sizeof(uint16_t) * line_header.class_set_size);
+    ProfileLoadSatus status = buffer.FillFromFd(fd, "ReadProfileLineClasses", error);
+    if (status != kProfileLoadSuccess) {
+      return status;
+    }
+    if (!ReadClasses(buffer, line_header.class_set_size, line_header, error)) {
+      return kProfileLoadBadData;
+    }
+  }
+
   return kProfileLoadSuccess;
 }
 
@@ -470,7 +715,7 @@
   if (status == kProfileLoadSuccess) {
     return true;
   } else {
-    PLOG(WARNING) << "Error when reading profile " << error;
+    LOG(WARNING) << "Error when reading profile: " << error;
     return false;
   }
 }
@@ -490,15 +735,16 @@
   if (stat_buffer.st_size == 0) {
     return kProfileLoadSuccess;
   }
-  // Read profile header: magic + version + number_of_lines.
-  uint16_t number_of_lines;
-  ProfileLoadSatus status = ReadProfileHeader(fd, &number_of_lines, error);
+  // Read profile header: magic + version + number_of_dex_files.
+  uint8_t number_of_dex_files;
+  ProfileLoadSatus status = ReadProfileHeader(fd, &number_of_dex_files, error);
   if (status != kProfileLoadSuccess) {
     return status;
   }
 
-  while (number_of_lines > 0) {
+  for (uint8_t k = 0; k < number_of_dex_files; k++) {
     ProfileLineHeader line_header;
+
     // First, read the line header to get the amount of data we need to read.
     status = ReadProfileLineHeader(fd, &line_header, error);
     if (status != kProfileLoadSuccess) {
@@ -506,11 +752,10 @@
     }
 
     // Now read the actual profile line.
-    status = ReadProfileLine(fd, line_header, error);
+    status = ReadProfileLine(fd, number_of_dex_files, line_header, error);
     if (status != kProfileLoadSuccess) {
       return status;
     }
-    number_of_lines--;
   }
 
   // Check that we read everything and that profiles don't contain junk data.
@@ -538,37 +783,116 @@
     }
   }
   // All checksums match. Import the data.
+
+  // The other profile might have a different indexing of dex files.
+  // That is because each dex files gets a 'dex_profile_index' on a first come first served basis.
+  // That means that the order in with the methods are added to the profile matters for the
+  // actual indices.
+  // The reason we cannot rely on the actual multidex index is that a single profile may store
+  // data from multiple splits. This means that a profile may contain a classes2.dex from split-A
+  // and one from split-B.
+
+  // First, build a mapping from other_dex_profile_index to this_dex_profile_index.
+  // This will make sure that the ClassReferences  will point to the correct dex file.
+  SafeMap<uint8_t, uint8_t> dex_profile_index_remap;
+  for (const auto& other_it : other.info_) {
+    const std::string& other_dex_location = other_it.first;
+    const DexFileData& other_dex_data = other_it.second;
+    auto info_it = info_.FindOrAdd(other_dex_location, DexFileData(other_dex_data.checksum, 0));
+    const DexFileData& dex_data = info_it->second;
+    dex_profile_index_remap.Put(other_dex_data.profile_index, dex_data.profile_index);
+  }
+
+  // Merge the actual profile data.
   for (const auto& other_it : other.info_) {
     const std::string& other_dex_location = other_it.first;
     const DexFileData& other_dex_data = other_it.second;
     auto info_it = info_.find(other_dex_location);
-    if (info_it == info_.end()) {
-      info_it = info_.Put(other_dex_location, DexFileData(other_dex_data.checksum));
-    }
-    info_it->second.method_set.insert(other_dex_data.method_set.begin(),
-                                      other_dex_data.method_set.end());
+    DCHECK(info_it != info_.end());
+
+    // Merge the classes.
     info_it->second.class_set.insert(other_dex_data.class_set.begin(),
                                      other_dex_data.class_set.end());
+
+    // Merge the methods and the inline caches.
+    for (const auto& other_method_it : other_dex_data.method_map) {
+      uint16_t other_method_index = other_method_it.first;
+      auto method_it = info_it->second.method_map.FindOrAdd(other_method_index);
+      const auto& other_inline_cache = other_method_it.second;
+      for (const auto& other_ic_it : other_inline_cache) {
+        uint16_t other_dex_pc = other_ic_it.first;
+        const ClassSet& other_class_set = other_ic_it.second.classes;
+        auto class_set = method_it->second.FindOrAdd(other_dex_pc);
+        if (other_ic_it.second.is_megamorphic) {
+          class_set->second.SetMegamorphic();
+        } else {
+          for (const auto& class_it : other_class_set) {
+            class_set->second.AddClass(dex_profile_index_remap.Get(
+                class_it.dex_profile_index), class_it.type_index);
+          }
+        }
+      }
+    }
   }
   return true;
 }
 
+static bool ChecksumMatch(uint32_t dex_file_checksum, uint32_t checksum) {
+  return kDebugIgnoreChecksum || dex_file_checksum == checksum;
+}
+
 static bool ChecksumMatch(const DexFile& dex_file, uint32_t checksum) {
-  return kDebugIgnoreChecksum || dex_file.GetLocationChecksum() == checksum;
+  return ChecksumMatch(dex_file.GetLocationChecksum(), checksum);
 }
 
 bool ProfileCompilationInfo::ContainsMethod(const MethodReference& method_ref) const {
-  auto info_it = info_.find(GetProfileDexFileKey(method_ref.dex_file->GetLocation()));
-  if (info_it != info_.end()) {
-    if (!ChecksumMatch(*method_ref.dex_file, info_it->second.checksum)) {
-      return false;
-    }
-    const std::set<uint16_t>& methods = info_it->second.method_set;
-    return methods.find(method_ref.dex_method_index) != methods.end();
-  }
-  return false;
+  return FindMethod(method_ref.dex_file->GetLocation(),
+                    method_ref.dex_file->GetLocationChecksum(),
+                    method_ref.dex_method_index) != nullptr;
 }
 
+const ProfileCompilationInfo::InlineCacheMap*
+ProfileCompilationInfo::FindMethod(const std::string& dex_location,
+                                   uint32_t dex_checksum,
+                                   uint16_t dex_method_index) const {
+  auto info_it = info_.find(GetProfileDexFileKey(dex_location));
+  if (info_it != info_.end()) {
+    if (!ChecksumMatch(dex_checksum, info_it->second.checksum)) {
+      return nullptr;
+    }
+    const MethodMap& methods = info_it->second.method_map;
+    const auto method_it = methods.find(dex_method_index);
+    return method_it == methods.end() ? nullptr : &(method_it->second);
+  }
+  return nullptr;
+}
+
+void ProfileCompilationInfo::DexFileToProfileIndex(
+    /*out*/std::vector<DexReference>* dex_references) const {
+  dex_references->resize(info_.size());
+  for (const auto& info_it : info_) {
+    DexReference& dex_ref = (*dex_references)[info_it.second.profile_index];
+    dex_ref.dex_location = info_it.first;
+    dex_ref.dex_checksum = info_it.second.checksum;
+  }
+}
+
+bool ProfileCompilationInfo::GetMethod(const std::string& dex_location,
+                                       uint32_t dex_checksum,
+                                       uint16_t dex_method_index,
+                                       /*out*/OfflineProfileMethodInfo* pmi) const {
+  const InlineCacheMap* inline_caches = FindMethod(dex_location, dex_checksum, dex_method_index);
+  if (inline_caches == nullptr) {
+    return false;
+  }
+
+  DexFileToProfileIndex(&pmi->dex_references);
+  // TODO(calin): maybe expose a direct pointer to avoid copying
+  pmi->inline_caches = *inline_caches;
+  return true;
+}
+
+
 bool ProfileCompilationInfo::ContainsClass(const DexFile& dex_file, dex::TypeIndex type_idx) const {
   auto info_it = info_.find(GetProfileDexFileKey(dex_file.GetLocation()));
   if (info_it != info_.end()) {
@@ -584,7 +908,7 @@
 uint32_t ProfileCompilationInfo::GetNumberOfMethods() const {
   uint32_t total = 0;
   for (const auto& it : info_) {
-    total += it.second.method_set.size();
+    total += it.second.method_map.size();
   }
   return total;
 }
@@ -645,19 +969,34 @@
       }
     }
     os << "\n\tmethods: ";
-    for (const auto method_it : dex_data.method_set) {
+    for (const auto method_it : dex_data.method_map) {
       if (dex_file != nullptr) {
-        os << "\n\t\t" << dex_file->PrettyMethod(method_it, true);
+        os << "\n\t\t" << dex_file->PrettyMethod(method_it.first, true);
       } else {
-        os << method_it << ",";
+        os << method_it.first;
       }
+
+      os << "[";
+      for (const auto& inline_cache_it : method_it.second) {
+        os << "{" << std::hex << inline_cache_it.first << std::dec << ":";
+        if (inline_cache_it.second.is_megamorphic) {
+          os << "M";
+        } else {
+          for (const ClassReference& class_ref : inline_cache_it.second.classes) {
+            os << "(" << static_cast<uint32_t>(class_ref.dex_profile_index)
+               << "," << class_ref.type_index.index_ << ")";
+          }
+        }
+        os << "}";
+      }
+      os << "], ";
     }
     os << "\n\tclasses: ";
     for (const auto class_it : dex_data.class_set) {
       if (dex_file != nullptr) {
         os << "\n\t\t" << dex_file->PrettyType(class_it);
       } else {
-        os << class_it << ",";
+        os << class_it.index_ << ",";
       }
     }
   }
@@ -762,4 +1101,44 @@
   return info.Save(fd);
 }
 
+bool ProfileCompilationInfo::OfflineProfileMethodInfo::operator==(
+      const OfflineProfileMethodInfo& other) const {
+  if (inline_caches.size() != other.inline_caches.size()) {
+    return false;
+  }
+
+  // We can't use a simple equality test because we need to match the dex files
+  // of the inline caches which might have different profile indices.
+  for (const auto& inline_cache_it : inline_caches) {
+    uint16_t dex_pc = inline_cache_it.first;
+    const DexPcData dex_pc_data = inline_cache_it.second;
+    const auto other_it = other.inline_caches.find(dex_pc);
+    if (other_it == other.inline_caches.end()) {
+      return false;
+    }
+    const DexPcData& other_dex_pc_data = other_it->second;
+    if (dex_pc_data.is_megamorphic != other_dex_pc_data.is_megamorphic) {
+      return false;
+    }
+    for (const ClassReference& class_ref : dex_pc_data.classes) {
+      bool found = false;
+      for (const ClassReference& other_class_ref : other_dex_pc_data.classes) {
+        CHECK_LE(class_ref.dex_profile_index, dex_references.size());
+        CHECK_LE(other_class_ref.dex_profile_index, other.dex_references.size());
+        const DexReference& dex_ref = dex_references[class_ref.dex_profile_index];
+        const DexReference& other_dex_ref = other.dex_references[other_class_ref.dex_profile_index];
+        if (class_ref.type_index == other_class_ref.type_index &&
+            dex_ref == other_dex_ref) {
+          found = true;
+          break;
+        }
+      }
+      if (!found) {
+        return false;
+      }
+    }
+  }
+  return true;
+}
+
 }  // namespace art
diff --git a/runtime/jit/profile_compilation_info.h b/runtime/jit/profile_compilation_info.h
index 758b46d..4bfbfcd 100644
--- a/runtime/jit/profile_compilation_info.h
+++ b/runtime/jit/profile_compilation_info.h
@@ -31,6 +31,40 @@
 namespace art {
 
 /**
+ *  Convenient class to pass around profile information (including inline caches)
+ *  without the need to hold GC-able objects.
+ */
+struct ProfileMethodInfo {
+  struct ProfileClassReference {
+    ProfileClassReference(const DexFile* dex, const dex::TypeIndex& index)
+        : dex_file(dex), type_index(index) {}
+
+    const DexFile* dex_file;
+    const dex::TypeIndex type_index;
+  };
+
+  struct ProfileInlineCache {
+    ProfileInlineCache(uint32_t pc, const std::vector<ProfileClassReference>& profile_classes)
+        : dex_pc(pc), classes(profile_classes) {}
+
+    const uint32_t dex_pc;
+    const std::vector<ProfileClassReference> classes;
+  };
+
+  ProfileMethodInfo(const DexFile* dex, uint32_t method_index)
+      : dex_file(dex), dex_method_index(method_index) {}
+
+  ProfileMethodInfo(const DexFile* dex,
+                    uint32_t method_index,
+                    const std::vector<ProfileInlineCache>& caches)
+      : dex_file(dex), dex_method_index(method_index), inline_caches(caches) {}
+
+  const DexFile* dex_file;
+  const uint32_t dex_method_index;
+  const std::vector<ProfileInlineCache> inline_caches;
+};
+
+/**
  * Profile information in a format suitable to be queried by the compiler and
  * performing profile guided compilation.
  * It is a serialize-friendly format based on information collected by the
@@ -42,34 +76,130 @@
   static const uint8_t kProfileMagic[];
   static const uint8_t kProfileVersion[];
 
+  // Data structures for encoding the offline representation of inline caches.
+  // This is exposed as public in order to make it available to dex2oat compilations
+  // (see compiler/optimizing/inliner.cc).
+
+  // A dex location together with its checksum.
+  struct DexReference {
+    DexReference() {}
+
+    DexReference(const std::string& location, uint32_t checksum)
+        : dex_location(location), dex_checksum(checksum) {}
+
+    bool operator==(const DexReference& other) const {
+      return dex_checksum == other.dex_checksum && dex_location == other.dex_location;
+    }
+
+    std::string dex_location;
+    uint32_t dex_checksum;
+  };
+
+  // Encodes a class reference in the profile.
+  // The owning dex file is encoded as the index (dex_profile_index) it has in the
+  // profile rather than as a full DexRefence(location,checksum).
+  // This avoids excessive string copying when managing the profile data.
+  // The dex_profile_index is an index in either of:
+  //  - OfflineProfileMethodInfo#dex_references vector (public use)
+  //  - DexFileData#profile_index (internal use).
+  // Note that the dex_profile_index is not necessary the multidex index.
+  // We cannot rely on the actual multidex index because a single profile may store
+  // data from multiple splits. This means that a profile may contain a classes2.dex from split-A
+  // and one from split-B.
+  struct ClassReference {
+    ClassReference(uint8_t dex_profile_idx, const dex::TypeIndex& type_idx) :
+      dex_profile_index(dex_profile_idx), type_index(type_idx) {}
+
+    bool operator==(const ClassReference& other) const {
+      return dex_profile_index == other.dex_profile_index && type_index == other.type_index;
+    }
+    bool operator<(const ClassReference& other) const {
+      return dex_profile_index == other.dex_profile_index
+          ? type_index < other.type_index
+          : dex_profile_index < other.dex_profile_index;
+    }
+
+    uint8_t dex_profile_index;  // the index of the owning dex in the profile info
+    dex::TypeIndex type_index;  // the type index of the class
+  };
+
+  // The set of classes that can be found at a given dex pc.
+  using ClassSet = std::set<ClassReference>;
+
+  // Encodes the actual inline cache for a given dex pc (whether or not the receiver is
+  // megamorphic and its possible types).
+  // If the receiver is megamorphic the set of classes will be empty.
+  struct DexPcData {
+    DexPcData() : is_megamorphic(false) {}
+    void AddClass(uint16_t dex_profile_idx, const dex::TypeIndex& type_idx);
+    void SetMegamorphic() {
+      is_megamorphic = true;
+      classes.clear();
+    }
+    bool operator==(const DexPcData& other) const {
+      return is_megamorphic == other.is_megamorphic && classes == other.classes;
+    }
+
+    bool is_megamorphic;
+    ClassSet classes;
+  };
+
+  // The inline cache map: DexPc -> DexPcData.
+  using InlineCacheMap = SafeMap<uint16_t, DexPcData>;
+
+  // Encodes the full set of inline caches for a given method.
+  // The dex_references vector is indexed according to the ClassReference::dex_profile_index.
+  // i.e. the dex file of any ClassReference present in the inline caches can be found at
+  // dex_references[ClassReference::dex_profile_index].
+  struct OfflineProfileMethodInfo {
+    bool operator==(const OfflineProfileMethodInfo& other) const;
+
+    std::vector<DexReference> dex_references;
+    InlineCacheMap inline_caches;
+  };
+
+  // Public methods to create, extend or query the profile.
+
   // Add the given methods and classes to the current profile object.
-  bool AddMethodsAndClasses(const std::vector<MethodReference>& methods,
+  bool AddMethodsAndClasses(const std::vector<ProfileMethodInfo>& methods,
                             const std::set<DexCacheResolvedClasses>& resolved_classes);
-  // Loads profile information from the given file descriptor.
+
+  // Load profile information from the given file descriptor.
   bool Load(int fd);
+
   // Merge the data from another ProfileCompilationInfo into the current object.
   bool MergeWith(const ProfileCompilationInfo& info);
-  // Saves the profile data to the given file descriptor.
+
+  // Save the profile data to the given file descriptor.
   bool Save(int fd);
-  // Loads and merges profile information from the given file into the current
+
+  // Load and merge profile information from the given file into the current
   // object and tries to save it back to disk.
   // If `force` is true then the save will go through even if the given file
   // has bad data or its version does not match. In this cases the profile content
   // is ignored.
   bool MergeAndSave(const std::string& filename, uint64_t* bytes_written, bool force);
 
-  // Returns the number of methods that were profiled.
+  // Return the number of methods that were profiled.
   uint32_t GetNumberOfMethods() const;
-  // Returns the number of resolved classes that were profiled.
+
+  // Return the number of resolved classes that were profiled.
   uint32_t GetNumberOfResolvedClasses() const;
 
-  // Returns true if the method reference is present in the profiling info.
+  // Return true if the method reference is present in the profiling info.
   bool ContainsMethod(const MethodReference& method_ref) const;
 
-  // Returns true if the class's type is present in the profiling info.
+  // Return true if the class's type is present in the profiling info.
   bool ContainsClass(const DexFile& dex_file, dex::TypeIndex type_idx) const;
 
-  // Dumps all the loaded profile info into a string and returns it.
+  // Return true if the method is present in the profiling info.
+  // If the method is found, `pmi` is populated with its inline caches.
+  bool GetMethod(const std::string& dex_location,
+                 uint32_t dex_checksum,
+                 uint16_t dex_method_index,
+                 /*out*/OfflineProfileMethodInfo* pmi) const;
+
+  // Dump all the loaded profile info into a string and returns it.
   // If dex_files is not null then the method indices will be resolved to their
   // names.
   // This is intended for testing and debugging.
@@ -80,26 +210,35 @@
 
   void GetClassNames(const std::vector<std::unique_ptr<const DexFile>>* dex_files,
                      std::set<std::string>* class_names) const;
+
   void GetClassNames(const std::vector<const DexFile*>* dex_files,
                      std::set<std::string>* class_names) const;
 
+  // Perform an equality test with the `other` profile information.
   bool Equals(const ProfileCompilationInfo& other);
 
-  static std::string GetProfileDexFileKey(const std::string& dex_location);
-
-  // Returns the class descriptors for all of the classes in the profiles' class sets.
+  // Return the class descriptors for all of the classes in the profiles' class sets.
   // Note the dex location is actually the profile key, the caller needs to call back in to the
   // profile info stuff to generate a map back to the dex location.
   std::set<DexCacheResolvedClasses> GetResolvedClasses() const;
 
-  // Clears the resolved classes from the current object.
+  // Clear the resolved classes from the current object.
   void ClearResolvedClasses();
 
+  // Return the profile key associated with the given dex location.
+  static std::string GetProfileDexFileKey(const std::string& dex_location);
+
+  // Generate a test profile which will contain a percentage of the total maximum
+  // number of methods and classes (method_ratio and class_ratio).
   static bool GenerateTestProfile(int fd,
                                   uint16_t number_of_dex_files,
                                   uint16_t method_ratio,
                                   uint16_t class_ratio);
 
+  // Check that the given profile method info contain the same data.
+  static bool Equals(const ProfileCompilationInfo::OfflineProfileMethodInfo& pmi1,
+                     const ProfileCompilationInfo::OfflineProfileMethodInfo& pmi2);
+
  private:
   enum ProfileLoadSatus {
     kProfileLoadIOError,
@@ -108,30 +247,71 @@
     kProfileLoadSuccess
   };
 
+  // Maps a method dex index to its inline cache.
+  using MethodMap = SafeMap<uint16_t, InlineCacheMap>;
+
+  // Internal representation of the profile information belonging to a dex file.
   struct DexFileData {
-    explicit DexFileData(uint32_t location_checksum) : checksum(location_checksum) {}
+    DexFileData(uint32_t location_checksum, uint16_t index)
+         : profile_index(index), checksum(location_checksum) {}
+    // The profile index of this dex file (matches ClassReference#dex_profile_index)
+    uint8_t profile_index;
+    // The dex checksum
     uint32_t checksum;
-    std::set<uint16_t> method_set;
+    // The methonds' profile information
+    MethodMap method_map;
+    // The classes which have been profiled. Note that these don't necessarily include
+    // all the classes that can be found in the inline caches reference.
     std::set<dex::TypeIndex> class_set;
 
     bool operator==(const DexFileData& other) const {
-      return checksum == other.checksum && method_set == other.method_set;
+      return checksum == other.checksum && method_map == other.method_map;
     }
   };
 
+  // Maps dex file to their profile information.
   using DexFileToProfileInfoMap = SafeMap<const std::string, DexFileData>;
 
+  // Return the profile data for the given dex location or null if the dex location
+  // already exists but has a different checksum
   DexFileData* GetOrAddDexFileData(const std::string& dex_location, uint32_t checksum);
+
+  // Add a method index to the profile (without inline caches).
   bool AddMethodIndex(const std::string& dex_location, uint32_t checksum, uint16_t method_idx);
+
+  // Add a method to the profile using its online representation (containing runtime structures).
+  bool AddMethod(const ProfileMethodInfo& pmi);
+
+  // Add a method to the profile using its offline representation.
+  // This is mostly used to facilitate testing.
+  bool AddMethod(const std::string& dex_location,
+                 uint32_t dex_checksum,
+                 uint16_t method_index,
+                 const OfflineProfileMethodInfo& pmi);
+
+  // Add a class index to the profile.
   bool AddClassIndex(const std::string& dex_location, uint32_t checksum, dex::TypeIndex type_idx);
+
+  // Add all classes from the given dex cache to the the profile.
   bool AddResolvedClasses(const DexCacheResolvedClasses& classes);
 
+  // Search for the given method in the profile.
+  // If found, its inline cache map is returned, otherwise the method returns null.
+  const InlineCacheMap* FindMethod(const std::string& dex_location,
+                                   uint32_t dex_checksum,
+                                   uint16_t dex_method_index) const;
+
+  // Encode the known dex_files into a vector. The index of a dex_reference will
+  // be the same as the profile index of the dex file (used to encode the ClassReferences).
+  void DexFileToProfileIndex(/*out*/std::vector<DexReference>* dex_references) const;
+
   // Parsing functionality.
 
+  // The information present in the header of each profile line.
   struct ProfileLineHeader {
     std::string dex_location;
-    uint16_t method_set_size;
     uint16_t class_set_size;
+    uint32_t method_region_size_bytes;
     uint32_t checksum;
   };
 
@@ -150,12 +330,15 @@
 
     // Reads an uint value (high bits to low bits) and advances the current pointer
     // with the number of bits read.
-    template <typename T> T ReadUintAndAdvance();
+    template <typename T> bool ReadUintAndAdvance(/*out*/ T* value);
 
     // Compares the given data with the content current pointer. If the contents are
     // equal it advances the current pointer by data_size.
     bool CompareAndAdvance(const uint8_t* data, size_t data_size);
 
+    // Returns true if the buffer has more data to read.
+    bool HasMoreData();
+
     // Get the underlying raw buffer.
     uint8_t* Get() { return storage_.get(); }
 
@@ -165,24 +348,63 @@
     uint8_t* ptr_end_;
   };
 
+  // Entry point for profile loding functionality.
   ProfileLoadSatus LoadInternal(int fd, std::string* error);
 
+  // Read the profile header from the given fd and store the number of profile
+  // lines into number_of_dex_files.
   ProfileLoadSatus ReadProfileHeader(int fd,
-                                     /*out*/uint16_t* number_of_lines,
+                                     /*out*/uint8_t* number_of_dex_files,
                                      /*out*/std::string* error);
 
+  // Read the header of a profile line from the given fd.
   ProfileLoadSatus ReadProfileLineHeader(int fd,
                                          /*out*/ProfileLineHeader* line_header,
                                          /*out*/std::string* error);
+
+  // Read individual elements from the profile line header.
+  bool ReadProfileLineHeaderElements(SafeBuffer& buffer,
+                                     /*out*/uint16_t* dex_location_size,
+                                     /*out*/ProfileLineHeader* line_header,
+                                     /*out*/std::string* error);
+
+  // Read a single profile line from the given fd.
   ProfileLoadSatus ReadProfileLine(int fd,
+                                   uint8_t number_of_dex_files,
                                    const ProfileLineHeader& line_header,
                                    /*out*/std::string* error);
 
-  bool ProcessLine(SafeBuffer& line_buffer,
-                   uint16_t method_set_size,
-                   uint16_t class_set_size,
-                   uint32_t checksum,
-                   const std::string& dex_location);
+  // Read all the classes from the buffer into the profile `info_` structure.
+  bool ReadClasses(SafeBuffer& buffer,
+                   uint16_t classes_to_read,
+                   const ProfileLineHeader& line_header,
+                   /*out*/std::string* error);
+
+  // Read all the methods from the buffer into the profile `info_` structure.
+  bool ReadMethods(SafeBuffer& buffer,
+                   uint8_t number_of_dex_files,
+                   const ProfileLineHeader& line_header,
+                   /*out*/std::string* error);
+
+  // Read the inline cache encoding from line_bufer into inline_cache.
+  bool ReadInlineCache(SafeBuffer& buffer,
+                       uint8_t number_of_dex_files,
+                       /*out*/InlineCacheMap* inline_cache,
+                       /*out*/std::string* error);
+
+  // Encode the inline cache into the given buffer.
+  void AddInlineCacheToBuffer(std::vector<uint8_t>* buffer,
+                              const InlineCacheMap& inline_cache);
+
+  // Return the number of bytes needed to encode the profile information
+  // for the methods in dex_data.
+  uint32_t GetMethodsRegionSize(const DexFileData& dex_data);
+
+  // Group `classes` by their owning dex profile index and put the result in
+  // `dex_to_classes_map`.
+  void GroupClassesByDex(
+      const ClassSet& classes,
+      /*out*/SafeMap<uint8_t, std::vector<dex::TypeIndex>>* dex_to_classes_map);
 
   friend class ProfileCompilationInfoTest;
   friend class CompilerDriverProfileTest;
diff --git a/runtime/jit/profile_compilation_info_test.cc b/runtime/jit/profile_compilation_info_test.cc
index 835a5f3..332280a 100644
--- a/runtime/jit/profile_compilation_info_test.cc
+++ b/runtime/jit/profile_compilation_info_test.cc
@@ -57,6 +57,14 @@
     return info->AddMethodIndex(dex_location, checksum, method_index);
   }
 
+  bool AddMethod(const std::string& dex_location,
+                 uint32_t checksum,
+                 uint16_t method_index,
+                 const ProfileCompilationInfo::OfflineProfileMethodInfo& pmi,
+                 ProfileCompilationInfo* info) {
+    return info->AddMethod(dex_location, checksum, method_index, pmi);
+  }
+
   bool AddClass(const std::string& dex_location,
                 uint32_t checksum,
                 uint16_t class_index,
@@ -73,17 +81,132 @@
       const std::vector<ArtMethod*>& methods,
       const std::set<DexCacheResolvedClasses>& resolved_classes) {
     ProfileCompilationInfo info;
-    std::vector<MethodReference> method_refs;
+    std::vector<ProfileMethodInfo> profile_methods;
     ScopedObjectAccess soa(Thread::Current());
     for (ArtMethod* method : methods) {
-      method_refs.emplace_back(method->GetDexFile(), method->GetDexMethodIndex());
+      profile_methods.emplace_back(method->GetDexFile(), method->GetDexMethodIndex());
     }
-    if (!info.AddMethodsAndClasses(method_refs, resolved_classes)) {
+    if (!info.AddMethodsAndClasses(profile_methods, resolved_classes)) {
+      return false;
+    }
+    if (info.GetNumberOfMethods() != profile_methods.size()) {
       return false;
     }
     return info.MergeAndSave(filename, nullptr, false);
   }
 
+  // Saves the given art methods to a profile backed by 'filename' and adds
+  // some fake inline caches to it. The added inline caches are returned in
+  // the out map `profile_methods_map`.
+  bool SaveProfilingInfoWithFakeInlineCaches(
+      const std::string& filename,
+      const std::vector<ArtMethod*>& methods,
+      /*out*/ SafeMap<ArtMethod*, ProfileMethodInfo>* profile_methods_map) {
+    ProfileCompilationInfo info;
+    std::vector<ProfileMethodInfo> profile_methods;
+    ScopedObjectAccess soa(Thread::Current());
+    for (ArtMethod* method : methods) {
+      std::vector<ProfileMethodInfo::ProfileInlineCache> caches;
+      // Monomorphic
+      for (uint16_t dex_pc = 0; dex_pc < 1; dex_pc++) {
+        std::vector<ProfileMethodInfo::ProfileClassReference> classes;
+        classes.emplace_back(method->GetDexFile(), dex::TypeIndex(0));
+        caches.emplace_back(dex_pc, classes);
+      }
+      // Polymorphic
+      for (uint16_t dex_pc = 1; dex_pc < 2; dex_pc++) {
+        std::vector<ProfileMethodInfo::ProfileClassReference> classes;
+        for (uint16_t k = 0; k < InlineCache::kIndividualCacheSize / 2; k++) {
+          classes.emplace_back(method->GetDexFile(), dex::TypeIndex(k));
+        }
+        caches.emplace_back(dex_pc, classes);
+      }
+      // Megamorphic
+      for (uint16_t dex_pc = 2; dex_pc < 3; dex_pc++) {
+        std::vector<ProfileMethodInfo::ProfileClassReference> classes;
+        for (uint16_t k = 0; k < 2 * InlineCache::kIndividualCacheSize; k++) {
+          classes.emplace_back(method->GetDexFile(), dex::TypeIndex(k));
+        }
+        caches.emplace_back(dex_pc, classes);
+      }
+      ProfileMethodInfo pmi(method->GetDexFile(), method->GetDexMethodIndex(), caches);
+      profile_methods.push_back(pmi);
+      profile_methods_map->Put(method, pmi);
+    }
+
+    if (!info.AddMethodsAndClasses(profile_methods, std::set<DexCacheResolvedClasses>())) {
+      return false;
+    }
+    if (info.GetNumberOfMethods() != profile_methods.size()) {
+      return false;
+    }
+    return info.MergeAndSave(filename, nullptr, false);
+  }
+
+  ProfileCompilationInfo::OfflineProfileMethodInfo ConvertProfileMethodInfo(
+        const ProfileMethodInfo& pmi) {
+    ProfileCompilationInfo::OfflineProfileMethodInfo offline_pmi;
+    SafeMap<DexFile*, uint8_t> dex_map;  // dex files to profile index
+    for (const auto& inline_cache : pmi.inline_caches) {
+      for (const auto& class_ref : inline_cache.classes) {
+        uint8_t dex_profile_index = dex_map.FindOrAdd(const_cast<DexFile*>(class_ref.dex_file),
+                                                      static_cast<uint8_t>(dex_map.size()))->second;
+        offline_pmi.inline_caches
+            .FindOrAdd(inline_cache.dex_pc)->second
+            .AddClass(dex_profile_index, class_ref.type_index);
+        if (dex_profile_index >= offline_pmi.dex_references.size()) {
+          // This is a new dex.
+          const std::string& dex_key = ProfileCompilationInfo::GetProfileDexFileKey(
+              class_ref.dex_file->GetLocation());
+          offline_pmi.dex_references.emplace_back(dex_key,
+                                                  class_ref.dex_file->GetLocationChecksum());
+        }
+      }
+    }
+    return offline_pmi;
+  }
+
+  // Creates an offline profile used for testing inline caches.
+  ProfileCompilationInfo::OfflineProfileMethodInfo GetOfflineProfileMethodInfo() {
+    ProfileCompilationInfo::OfflineProfileMethodInfo pmi;
+
+    pmi.dex_references.emplace_back("dex_location1", /* checksum */ 1);
+    pmi.dex_references.emplace_back("dex_location2", /* checksum */ 2);
+    pmi.dex_references.emplace_back("dex_location3", /* checksum */ 3);
+
+    // Monomorphic
+    for (uint16_t dex_pc = 0; dex_pc < 10; dex_pc++) {
+      ProfileCompilationInfo::DexPcData dex_pc_data;
+      dex_pc_data.AddClass(0, dex::TypeIndex(0));
+      pmi.inline_caches.Put(dex_pc, dex_pc_data);
+    }
+    // Polymorphic
+    for (uint16_t dex_pc = 10; dex_pc < 20; dex_pc++) {
+      ProfileCompilationInfo::DexPcData dex_pc_data;
+      dex_pc_data.AddClass(0, dex::TypeIndex(0));
+      dex_pc_data.AddClass(1, dex::TypeIndex(1));
+      dex_pc_data.AddClass(2, dex::TypeIndex(2));
+
+       pmi.inline_caches.Put(dex_pc, dex_pc_data);
+    }
+    // Megamorphic
+    for (uint16_t dex_pc = 20; dex_pc < 30; dex_pc++) {
+      ProfileCompilationInfo::DexPcData dex_pc_data;
+      dex_pc_data.is_megamorphic = true;
+      pmi.inline_caches.Put(dex_pc, dex_pc_data);
+    }
+
+    return pmi;
+  }
+
+  void MakeMegamorphic(/*out*/ProfileCompilationInfo::OfflineProfileMethodInfo* pmi) {
+    for (auto it : pmi->inline_caches) {
+      for (uint16_t k = 0; k <= 2 * InlineCache::kIndividualCacheSize; k++) {
+        it.second.AddClass(0, dex::TypeIndex(k));
+      }
+    }
+  }
+
   // Cannot sizeof the actual arrays so hardcode the values here.
   // They should not change anyway.
   static constexpr int kProfileMagicSize = 4;
@@ -235,12 +358,12 @@
 TEST_F(ProfileCompilationInfoTest, LoadEmpty) {
   ScratchFile profile;
 
-  ProfileCompilationInfo empyt_info;
+  ProfileCompilationInfo empty_info;
 
   ProfileCompilationInfo loaded_info;
   ASSERT_TRUE(profile.GetFile()->ResetOffset());
   ASSERT_TRUE(loaded_info.Load(GetFd(profile)));
-  ASSERT_TRUE(loaded_info.Equals(empyt_info));
+  ASSERT_TRUE(loaded_info.Equals(empty_info));
 }
 
 TEST_F(ProfileCompilationInfoTest, BadMagic) {
@@ -324,4 +447,243 @@
   ASSERT_FALSE(loaded_info.Load(GetFd(profile)));
 }
 
+TEST_F(ProfileCompilationInfoTest, SaveInlineCaches) {
+  ScratchFile profile;
+
+  ProfileCompilationInfo saved_info;
+  ProfileCompilationInfo::OfflineProfileMethodInfo pmi = GetOfflineProfileMethodInfo();
+
+  // Add methods with inline caches.
+  for (uint16_t method_idx = 0; method_idx < 10; method_idx++) {
+    // Add a method which is part of the same dex file as one of the
+    // class from the inline caches.
+    ASSERT_TRUE(AddMethod("dex_location1", /* checksum */ 1, method_idx, pmi, &saved_info));
+    // Add a method which is outside the set of dex files.
+    ASSERT_TRUE(AddMethod("dex_location4", /* checksum */ 4, method_idx, pmi, &saved_info));
+  }
+
+  ASSERT_TRUE(saved_info.Save(GetFd(profile)));
+  ASSERT_EQ(0, profile.GetFile()->Flush());
+
+  // Check that we get back what we saved.
+  ProfileCompilationInfo loaded_info;
+  ASSERT_TRUE(profile.GetFile()->ResetOffset());
+  ASSERT_TRUE(loaded_info.Load(GetFd(profile)));
+
+  ASSERT_TRUE(loaded_info.Equals(saved_info));
+
+  ProfileCompilationInfo::OfflineProfileMethodInfo loaded_pmi1;
+  ASSERT_TRUE(loaded_info.GetMethod("dex_location1",
+                                    /* checksum */ 1,
+                                    /* method_idx */ 3,
+                                    &loaded_pmi1));
+  ASSERT_TRUE(loaded_pmi1 == pmi);
+  ProfileCompilationInfo::OfflineProfileMethodInfo loaded_pmi2;
+  ASSERT_TRUE(loaded_info.GetMethod("dex_location4",
+                                    /* checksum */ 4,
+                                    /* method_idx */ 3,
+                                    &loaded_pmi2));
+  ASSERT_TRUE(loaded_pmi2 == pmi);
+}
+
+TEST_F(ProfileCompilationInfoTest, MegamorphicInlineCaches) {
+  ScratchFile profile;
+
+  ProfileCompilationInfo saved_info;
+  ProfileCompilationInfo::OfflineProfileMethodInfo pmi = GetOfflineProfileMethodInfo();
+
+  // Add methods with inline caches.
+  for (uint16_t method_idx = 0; method_idx < 10; method_idx++) {
+    ASSERT_TRUE(AddMethod("dex_location1", /* checksum */ 1, method_idx, pmi, &saved_info));
+  }
+
+  ASSERT_TRUE(saved_info.Save(GetFd(profile)));
+  ASSERT_EQ(0, profile.GetFile()->Flush());
+
+  // Make the inline caches megamorphic and add them to the profile again.
+  ProfileCompilationInfo saved_info_extra;
+  ProfileCompilationInfo::OfflineProfileMethodInfo pmi_extra = GetOfflineProfileMethodInfo();
+  MakeMegamorphic(&pmi_extra);
+  for (uint16_t method_idx = 0; method_idx < 10; method_idx++) {
+    ASSERT_TRUE(AddMethod("dex_location1", /* checksum */ 1, method_idx, pmi, &saved_info_extra));
+  }
+
+  ASSERT_TRUE(profile.GetFile()->ResetOffset());
+  ASSERT_TRUE(saved_info_extra.Save(GetFd(profile)));
+  ASSERT_EQ(0, profile.GetFile()->Flush());
+
+  // Merge the profiles so that we have the same view as the file.
+  ASSERT_TRUE(saved_info.MergeWith(saved_info_extra));
+
+  // Check that we get back what we saved.
+  ProfileCompilationInfo loaded_info;
+  ASSERT_TRUE(profile.GetFile()->ResetOffset());
+  ASSERT_TRUE(loaded_info.Load(GetFd(profile)));
+
+  ASSERT_TRUE(loaded_info.Equals(saved_info));
+
+  ProfileCompilationInfo::OfflineProfileMethodInfo loaded_pmi1;
+  ASSERT_TRUE(loaded_info.GetMethod("dex_location1",
+                                    /* checksum */ 1,
+                                    /* method_idx */ 3,
+                                    &loaded_pmi1));
+  ASSERT_TRUE(loaded_pmi1 == pmi_extra);
+}
+
+TEST_F(ProfileCompilationInfoTest, SaveArtMethodsWithInlineCaches) {
+  ScratchFile profile;
+
+  Thread* self = Thread::Current();
+  jobject class_loader;
+  {
+    ScopedObjectAccess soa(self);
+    class_loader = LoadDex("ProfileTestMultiDex");
+  }
+  ASSERT_NE(class_loader, nullptr);
+
+  // Save virtual methods from Main.
+  std::set<DexCacheResolvedClasses> resolved_classes;
+  std::vector<ArtMethod*> main_methods = GetVirtualMethods(class_loader, "LMain;");
+
+  SafeMap<ArtMethod*, ProfileMethodInfo> profile_methods_map;
+  ASSERT_TRUE(SaveProfilingInfoWithFakeInlineCaches(
+      profile.GetFilename(), main_methods,  &profile_methods_map));
+
+  // Check that what we saved is in the profile.
+  ProfileCompilationInfo info;
+  ASSERT_TRUE(info.Load(GetFd(profile)));
+  ASSERT_EQ(info.GetNumberOfMethods(), main_methods.size());
+  {
+    ScopedObjectAccess soa(self);
+    for (ArtMethod* m : main_methods) {
+      ASSERT_TRUE(info.ContainsMethod(MethodReference(m->GetDexFile(), m->GetDexMethodIndex())));
+      const ProfileMethodInfo& pmi = profile_methods_map.find(m)->second;
+      ProfileCompilationInfo::OfflineProfileMethodInfo offline_pmi;
+      ASSERT_TRUE(info.GetMethod(m->GetDexFile()->GetLocation(),
+                                 m->GetDexFile()->GetLocationChecksum(),
+                                 m->GetDexMethodIndex(),
+                                 &offline_pmi));
+      ProfileCompilationInfo::OfflineProfileMethodInfo converted_pmi =
+          ConvertProfileMethodInfo(pmi);
+      ASSERT_EQ(converted_pmi, offline_pmi);
+    }
+  }
+}
+
+TEST_F(ProfileCompilationInfoTest, InvalidChecksumInInlineCahce) {
+  ScratchFile profile;
+
+  ProfileCompilationInfo info;
+  ProfileCompilationInfo::OfflineProfileMethodInfo pmi1 = GetOfflineProfileMethodInfo();
+  ProfileCompilationInfo::OfflineProfileMethodInfo pmi2 = GetOfflineProfileMethodInfo();
+  // Modify the checksum to trigger a mismatch.
+  pmi2.dex_references[0].dex_checksum++;
+
+  ASSERT_TRUE(AddMethod("dex_location1", /* checksum */ 1, /*method_idx*/ 0, pmi1, &info));
+  ASSERT_FALSE(AddMethod("dex_location2", /* checksum */ 2, /*method_idx*/ 0, pmi2, &info));
+}
+
+// Verify that profiles behave correctly even if the methods are added in a different
+// order and with a different dex profile indices for the dex files.
+TEST_F(ProfileCompilationInfoTest, MergeInlineCacheTriggerReindex) {
+  ScratchFile profile;
+
+  ProfileCompilationInfo info;
+  ProfileCompilationInfo info_reindexed;
+
+  ProfileCompilationInfo::OfflineProfileMethodInfo pmi;
+  pmi.dex_references.emplace_back("dex_location1", /* checksum */ 1);
+  pmi.dex_references.emplace_back("dex_location2", /* checksum */ 2);
+  for (uint16_t dex_pc = 1; dex_pc < 5; dex_pc++) {
+    ProfileCompilationInfo::DexPcData dex_pc_data;
+    dex_pc_data.AddClass(0, dex::TypeIndex(0));
+    dex_pc_data.AddClass(1, dex::TypeIndex(1));
+    pmi.inline_caches.Put(dex_pc, dex_pc_data);
+  }
+
+  ProfileCompilationInfo::OfflineProfileMethodInfo pmi_reindexed;
+  pmi_reindexed.dex_references.emplace_back("dex_location2", /* checksum */ 2);
+  pmi_reindexed.dex_references.emplace_back("dex_location1", /* checksum */ 1);
+  for (uint16_t dex_pc = 1; dex_pc < 5; dex_pc++) {
+    ProfileCompilationInfo::DexPcData dex_pc_data;
+    dex_pc_data.AddClass(1, dex::TypeIndex(0));
+    dex_pc_data.AddClass(0, dex::TypeIndex(1));
+    pmi_reindexed.inline_caches.Put(dex_pc, dex_pc_data);
+  }
+
+  // Profile 1 and Profile 2 get the same methods but in different order.
+  // This will trigger a different dex numbers.
+  for (uint16_t method_idx = 0; method_idx < 10; method_idx++) {
+    ASSERT_TRUE(AddMethod("dex_location1", /* checksum */ 1, method_idx, pmi, &info));
+    ASSERT_TRUE(AddMethod("dex_location2", /* checksum */ 2, method_idx, pmi, &info));
+  }
+
+  for (uint16_t method_idx = 0; method_idx < 10; method_idx++) {
+    ASSERT_TRUE(AddMethod(
+      "dex_location2", /* checksum */ 2, method_idx, pmi_reindexed, &info_reindexed));
+    ASSERT_TRUE(AddMethod(
+      "dex_location1", /* checksum */ 1, method_idx, pmi_reindexed, &info_reindexed));
+  }
+
+  ProfileCompilationInfo info_backup = info;
+  ASSERT_TRUE(info.MergeWith(info_reindexed));
+  // Merging should have no effect as we're adding the exact same stuff.
+  ASSERT_TRUE(info.Equals(info_backup));
+  for (uint16_t method_idx = 0; method_idx < 10; method_idx++) {
+    ProfileCompilationInfo::OfflineProfileMethodInfo loaded_pmi1;
+    ASSERT_TRUE(info.GetMethod("dex_location1",
+                                      /* checksum */ 1,
+                                      /* method_idx */ method_idx,
+                                      &loaded_pmi1));
+    ASSERT_TRUE(loaded_pmi1 == pmi);
+    ProfileCompilationInfo::OfflineProfileMethodInfo loaded_pmi2;
+    ASSERT_TRUE(info.GetMethod("dex_location2",
+                                      /* checksum */ 2,
+                                      /* method_idx */ method_idx,
+                                      &loaded_pmi2));
+    ASSERT_TRUE(loaded_pmi2 == pmi);
+  }
+}
+
+TEST_F(ProfileCompilationInfoTest, AddMoreDexFileThanLimit) {
+  ProfileCompilationInfo info;
+  // Save a few methods.
+  for (uint16_t i = 0; i < std::numeric_limits<uint8_t>::max(); i++) {
+    std::string dex_location = std::to_string(i);
+    ASSERT_TRUE(AddMethod(dex_location, /* checksum */ 1, /* method_idx */ i, &info));
+  }
+  // We only support at most 255 dex files.
+  ASSERT_FALSE(AddMethod(
+      /*dex_location*/ "256", /* checksum */ 1, /* method_idx */ 0, &info));
+}
+
+TEST_F(ProfileCompilationInfoTest, MegamorphicInlineCachesMerge) {
+  // Create a megamorphic inline cache.
+  ProfileCompilationInfo::OfflineProfileMethodInfo pmi;
+  pmi.dex_references.emplace_back("dex_location1", /* checksum */ 1);
+  ProfileCompilationInfo::DexPcData dex_pc_data;
+  dex_pc_data.is_megamorphic = true;
+  pmi.inline_caches.Put(/*dex_pc*/ 0, dex_pc_data);
+
+  ProfileCompilationInfo info_megamorphic;
+  ASSERT_TRUE(AddMethod("dex_location1",
+                        /*checksum*/ 1,
+                        /*method_idx*/ 0,
+                        pmi,
+                        &info_megamorphic));
+
+  // Create a profile with no inline caches (for the same method).
+  ProfileCompilationInfo info_no_inline_cache;
+  ASSERT_TRUE(AddMethod("dex_location1",
+                        /*checksum*/ 1,
+                        /*method_idx*/ 0,
+                        &info_no_inline_cache));
+
+  // Merge the megamorphic cache into the empty one.
+  ASSERT_TRUE(info_no_inline_cache.MergeWith(info_megamorphic));
+  ScratchFile profile;
+  // Saving profile should work without crashing (b/35644850).
+  ASSERT_TRUE(info_no_inline_cache.Save(GetFd(profile)));
+}
+
 }  // namespace art
diff --git a/runtime/jit/profile_saver.cc b/runtime/jit/profile_saver.cc
index 025d10c..61e6c41 100644
--- a/runtime/jit/profile_saver.cc
+++ b/runtime/jit/profile_saver.cc
@@ -236,10 +236,10 @@
     std::set<DexCacheResolvedClasses> resolved_classes_for_location;
     const std::string& filename = it.first;
     const std::set<std::string>& locations = it.second;
-    std::vector<MethodReference> methods_for_location;
+    std::vector<ProfileMethodInfo> profile_methods_for_location;
     for (const MethodReference& ref : methods) {
       if (locations.find(ref.dex_file->GetBaseLocation()) != locations.end()) {
-        methods_for_location.push_back(ref);
+        profile_methods_for_location.emplace_back(ref.dex_file, ref.dex_method_index);
       }
     }
     for (const DexCacheResolvedClasses& classes : resolved_classes) {
@@ -253,7 +253,7 @@
       }
     }
     ProfileCompilationInfo* info = GetCachedProfiledInfo(filename);
-    info->AddMethodsAndClasses(methods_for_location, resolved_classes_for_location);
+    info->AddMethodsAndClasses(profile_methods_for_location, resolved_classes_for_location);
     total_number_of_profile_entries_cached += resolved_classes_for_location.size();
   }
   max_number_of_profile_entries_cached_ = std::max(
@@ -280,15 +280,15 @@
     }
     const std::string& filename = it.first;
     const std::set<std::string>& locations = it.second;
-    std::vector<MethodReference> methods;
+    std::vector<ProfileMethodInfo> profile_methods;
     {
       ScopedObjectAccess soa(Thread::Current());
-      jit_code_cache_->GetProfiledMethods(locations, methods);
+      jit_code_cache_->GetProfiledMethods(locations, profile_methods);
       total_number_of_code_cache_queries_++;
     }
 
     ProfileCompilationInfo* cached_info = GetCachedProfiledInfo(filename);
-    cached_info->AddMethodsAndClasses(methods, std::set<DexCacheResolvedClasses>());
+    cached_info->AddMethodsAndClasses(profile_methods, std::set<DexCacheResolvedClasses>());
     int64_t delta_number_of_methods =
         cached_info->GetNumberOfMethods() -
         static_cast<int64_t>(last_save_number_of_methods_);
diff --git a/runtime/jit/profiling_info.h b/runtime/jit/profiling_info.h
index 1c58a83..f42a8da 100644
--- a/runtime/jit/profiling_info.h
+++ b/runtime/jit/profiling_info.h
@@ -39,7 +39,7 @@
 // Once the classes_ array is full, we consider the INVOKE to be megamorphic.
 class InlineCache {
  public:
-  static constexpr uint16_t kIndividualCacheSize = 5;
+  static constexpr uint8_t kIndividualCacheSize = 5;
 
  private:
   uint32_t dex_pc_;
diff --git a/runtime/jni_internal.cc b/runtime/jni_internal.cc
index 547b5b8..5418d35 100644
--- a/runtime/jni_internal.cc
+++ b/runtime/jni_internal.cc
@@ -2265,7 +2265,18 @@
 
       VLOG(jni) << "[Registering JNI native method " << m->PrettyMethod() << "]";
 
-      is_fast = is_fast || m->IsFastNative();  // Merge with @FastNative state.
+      if (UNLIKELY(is_fast)) {
+        // There are a few reasons to switch:
+        // 1) We don't support !bang JNI anymore, it will turn to a hard error later.
+        // 2) @FastNative is actually faster. At least 1.5x faster than !bang JNI.
+        //    and switching is super easy, remove ! in C code, add annotation in .java code.
+        // 3) Good chance of hitting DCHECK failures in ScopedFastNativeObjectAccess
+        //    since that checks for presence of @FastNative and not for ! in the descriptor.
+        LOG(WARNING) << "!bang JNI is deprecated. Switch to @FastNative for " << m->PrettyMethod();
+        is_fast = false;
+        // TODO: make this a hard register error in the future.
+      }
+
       m->RegisterNative(fnPtr, is_fast);
     }
     return JNI_OK;
diff --git a/runtime/jni_internal.h b/runtime/jni_internal.h
index b3837c4..580a42b 100644
--- a/runtime/jni_internal.h
+++ b/runtime/jni_internal.h
@@ -19,20 +19,10 @@
 
 #include <jni.h>
 #include <iosfwd>
+#include "nativehelper/jni_macros.h"
 
 #include "base/macros.h"
 
-#ifndef NATIVE_METHOD
-#define NATIVE_METHOD(className, functionName, signature) \
-  { #functionName, signature, reinterpret_cast<void*>(className ## _ ## functionName) }
-#endif
-
-// TODO: Can we do a better job of supporting overloading ?
-#ifndef OVERLOADED_NATIVE_METHOD
-#define OVERLOADED_NATIVE_METHOD(className, functionName, signature, identifier) \
-    { #functionName, signature, reinterpret_cast<void*>(className ## _ ## identifier) }
-#endif
-
 #define REGISTER_NATIVE_METHODS(jni_class_name) \
   RegisterNativeMethods(env, jni_class_name, gMethods, arraysize(gMethods))
 
diff --git a/runtime/mirror/class.cc b/runtime/mirror/class.cc
index 9a9a5d8..eb2ec9b 100644
--- a/runtime/mirror/class.cc
+++ b/runtime/mirror/class.cc
@@ -951,7 +951,8 @@
     return interfaces->Get(idx);
   } else {
     dex::TypeIndex type_idx = klass->GetDirectInterfaceTypeIdx(idx);
-    ObjPtr<Class> interface = klass->GetDexCache()->GetResolvedType(type_idx);
+    ObjPtr<Class> interface = ClassLinker::LookupResolvedType(
+        type_idx, klass->GetDexCache(), klass->GetClassLoader());
     return interface;
   }
 }
diff --git a/runtime/mirror/dex_cache-inl.h b/runtime/mirror/dex_cache-inl.h
index 973c8ed..29bf6a0 100644
--- a/runtime/mirror/dex_cache-inl.h
+++ b/runtime/mirror/dex_cache-inl.h
@@ -41,14 +41,22 @@
   return Class::ComputeClassSize(true, vtable_entries, 0, 0, 0, 0, 0, pointer_size);
 }
 
-inline mirror::String* DexCache::GetResolvedString(dex::StringIndex string_idx) {
+inline uint32_t DexCache::StringSlotIndex(dex::StringIndex string_idx) {
   DCHECK_LT(string_idx.index_, GetDexFile()->NumStringIds());
-  return StringDexCachePair::Lookup(GetStrings(), string_idx.index_, NumStrings()).Read();
+  const uint32_t slot_idx = string_idx.index_ % kDexCacheStringCacheSize;
+  DCHECK_LT(slot_idx, NumStrings());
+  return slot_idx;
 }
 
-inline void DexCache::SetResolvedString(dex::StringIndex string_idx,
-                                        ObjPtr<mirror::String> resolved) {
-  StringDexCachePair::Assign(GetStrings(), string_idx.index_, resolved.Ptr(), NumStrings());
+inline String* DexCache::GetResolvedString(dex::StringIndex string_idx) {
+  return GetStrings()[StringSlotIndex(string_idx)].load(
+      std::memory_order_relaxed).GetObjectForIndex(string_idx.index_);
+}
+
+inline void DexCache::SetResolvedString(dex::StringIndex string_idx, ObjPtr<String> resolved) {
+  DCHECK(resolved != nullptr);
+  GetStrings()[StringSlotIndex(string_idx)].store(
+      StringDexCachePair(resolved, string_idx.index_), std::memory_order_relaxed);
   Runtime* const runtime = Runtime::Current();
   if (UNLIKELY(runtime->IsActiveTransaction())) {
     DCHECK(runtime->IsAotCompiler());
@@ -59,50 +67,70 @@
 }
 
 inline void DexCache::ClearString(dex::StringIndex string_idx) {
-  const uint32_t slot_idx = string_idx.index_ % NumStrings();
   DCHECK(Runtime::Current()->IsAotCompiler());
+  uint32_t slot_idx = StringSlotIndex(string_idx);
   StringDexCacheType* slot = &GetStrings()[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));
+    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;
+}
+
 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.
-  DCHECK_LT(type_idx.index_, NumResolvedTypes());
-  return GetResolvedTypes()[type_idx.index_].Read();
+  return GetResolvedTypes()[TypeSlotIndex(type_idx)].load(
+      std::memory_order_relaxed).GetObjectForIndex(type_idx.index_);
 }
 
 inline void DexCache::SetResolvedType(dex::TypeIndex type_idx, ObjPtr<Class> resolved) {
-  DCHECK_LT(type_idx.index_, NumResolvedTypes());  // NOTE: Unchecked, i.e. not throwing AIOOB.
+  DCHECK(resolved != nullptr);
   // 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.
-  reinterpret_cast<Atomic<GcRoot<mirror::Class>>&>(GetResolvedTypes()[type_idx.index_]).
-      StoreRelease(GcRoot<Class>(resolved));
+  GetResolvedTypes()[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.
   Runtime::Current()->GetHeap()->WriteBarrierEveryFieldOf(this);
 }
 
-inline MethodType* DexCache::GetResolvedMethodType(uint32_t proto_idx) {
+inline void DexCache::ClearResolvedType(dex::TypeIndex type_idx) {
+  DCHECK(Runtime::Current()->IsAotCompiler());
+  uint32_t slot_idx = TypeSlotIndex(type_idx);
+  TypeDexCacheType* slot = &GetResolvedTypes()[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(uint32_t proto_idx) {
   DCHECK(Runtime::Current()->IsMethodHandlesEnabled());
   DCHECK_LT(proto_idx, GetDexFile()->NumProtoIds());
-  return MethodTypeDexCachePair::Lookup(
-      GetResolvedMethodTypes(), proto_idx, NumResolvedMethodTypes()).Read();
+  const uint32_t slot_idx = proto_idx % kDexCacheMethodTypeCacheSize;
+  DCHECK_LT(slot_idx, NumResolvedMethodTypes());
+  return slot_idx;
+}
+
+inline MethodType* DexCache::GetResolvedMethodType(uint32_t proto_idx) {
+  return GetResolvedMethodTypes()[MethodTypeSlotIndex(proto_idx)].load(
+      std::memory_order_relaxed).GetObjectForIndex(proto_idx);
 }
 
 inline void DexCache::SetResolvedMethodType(uint32_t proto_idx, MethodType* resolved) {
-  DCHECK(Runtime::Current()->IsMethodHandlesEnabled());
-  DCHECK_LT(proto_idx, GetDexFile()->NumProtoIds());
-
-  MethodTypeDexCachePair::Assign(GetResolvedMethodTypes(), proto_idx, resolved,
-                                 NumResolvedMethodTypes());
+  DCHECK(resolved != nullptr);
+  GetResolvedMethodTypes()[MethodTypeSlotIndex(proto_idx)].store(
+      MethodTypeDexCachePair(resolved, proto_idx), std::memory_order_relaxed);
   // TODO: Fine-grained marking, so that we don't need to go through all arrays in full.
   Runtime::Current()->GetHeap()->WriteBarrierEveryFieldOf(this);
 }
@@ -228,15 +256,13 @@
   VisitInstanceFieldsReferences<kVerifyFlags, kReadBarrierOption>(klass, visitor);
   // Visit arrays after.
   if (kVisitNativeRoots) {
-    VisitDexCachePairs<mirror::String, kReadBarrierOption, Visitor>(
+    VisitDexCachePairs<String, kReadBarrierOption, Visitor>(
         GetStrings(), NumStrings(), visitor);
 
-    GcRoot<mirror::Class>* resolved_types = GetResolvedTypes();
-    for (size_t i = 0, num_types = NumResolvedTypes(); i != num_types; ++i) {
-      visitor.VisitRootIfNonNull(resolved_types[i].AddressWithoutBarrier());
-    }
+    VisitDexCachePairs<Class, kReadBarrierOption, Visitor>(
+        GetResolvedTypes(), NumResolvedTypes(), visitor);
 
-    VisitDexCachePairs<mirror::MethodType, kReadBarrierOption, Visitor>(
+    VisitDexCachePairs<MethodType, kReadBarrierOption, Visitor>(
         GetResolvedMethodTypes(), NumResolvedMethodTypes(), visitor);
 
     GcRoot<mirror::CallSite>* resolved_call_sites = GetResolvedCallSites();
@@ -247,35 +273,37 @@
 }
 
 template <ReadBarrierOption kReadBarrierOption, typename Visitor>
-inline void DexCache::FixupStrings(mirror::StringDexCacheType* dest, const Visitor& visitor) {
-  mirror::StringDexCacheType* src = GetStrings();
+inline void DexCache::FixupStrings(StringDexCacheType* dest, const Visitor& visitor) {
+  StringDexCacheType* src = GetStrings();
   for (size_t i = 0, count = NumStrings(); i < count; ++i) {
     StringDexCachePair source = src[i].load(std::memory_order_relaxed);
-    mirror::String* ptr = source.object.Read<kReadBarrierOption>();
-    mirror::String* new_source = visitor(ptr);
+    String* ptr = source.object.Read<kReadBarrierOption>();
+    String* new_source = visitor(ptr);
     source.object = GcRoot<String>(new_source);
     dest[i].store(source, std::memory_order_relaxed);
   }
 }
 
 template <ReadBarrierOption kReadBarrierOption, typename Visitor>
-inline void DexCache::FixupResolvedTypes(GcRoot<mirror::Class>* dest, const Visitor& visitor) {
-  GcRoot<mirror::Class>* src = GetResolvedTypes();
+inline void DexCache::FixupResolvedTypes(TypeDexCacheType* dest, const Visitor& visitor) {
+  TypeDexCacheType* src = GetResolvedTypes();
   for (size_t i = 0, count = NumResolvedTypes(); i < count; ++i) {
-    mirror::Class* source = src[i].Read<kReadBarrierOption>();
-    mirror::Class* new_source = visitor(source);
-    dest[i] = GcRoot<mirror::Class>(new_source);
+    TypeDexCachePair source = src[i].load(std::memory_order_relaxed);
+    Class* ptr = source.object.Read<kReadBarrierOption>();
+    Class* new_source = visitor(ptr);
+    source.object = GcRoot<Class>(new_source);
+    dest[i].store(source, std::memory_order_relaxed);
   }
 }
 
 template <ReadBarrierOption kReadBarrierOption, typename Visitor>
-inline void DexCache::FixupResolvedMethodTypes(mirror::MethodTypeDexCacheType* dest,
+inline void DexCache::FixupResolvedMethodTypes(MethodTypeDexCacheType* dest,
                                                const Visitor& visitor) {
-  mirror::MethodTypeDexCacheType* src = GetResolvedMethodTypes();
+  MethodTypeDexCacheType* src = GetResolvedMethodTypes();
   for (size_t i = 0, count = NumResolvedMethodTypes(); i < count; ++i) {
     MethodTypeDexCachePair source = src[i].load(std::memory_order_relaxed);
-    mirror::MethodType* ptr = source.object.Read<kReadBarrierOption>();
-    mirror::MethodType* new_source = visitor(ptr);
+    MethodType* ptr = source.object.Read<kReadBarrierOption>();
+    MethodType* new_source = visitor(ptr);
     source.object = GcRoot<MethodType>(new_source);
     dest[i].store(source, std::memory_order_relaxed);
   }
diff --git a/runtime/mirror/dex_cache.cc b/runtime/mirror/dex_cache.cc
index 0f6acab..1b8b391 100644
--- a/runtime/mirror/dex_cache.cc
+++ b/runtime/mirror/dex_cache.cc
@@ -58,8 +58,8 @@
 
   mirror::StringDexCacheType* strings = (dex_file->NumStringIds() == 0u) ? nullptr :
       reinterpret_cast<mirror::StringDexCacheType*>(raw_arrays + layout.StringsOffset());
-  GcRoot<mirror::Class>* types = (dex_file->NumTypeIds() == 0u) ? nullptr :
-      reinterpret_cast<GcRoot<mirror::Class>*>(raw_arrays + layout.TypesOffset());
+  mirror::TypeDexCacheType* types = (dex_file->NumTypeIds() == 0u) ? nullptr :
+      reinterpret_cast<mirror::TypeDexCacheType*>(raw_arrays + layout.TypesOffset());
   ArtMethod** methods = (dex_file->NumMethodIds() == 0u) ? nullptr :
       reinterpret_cast<ArtMethod**>(raw_arrays + layout.MethodsOffset());
   ArtField** fields = (dex_file->NumFieldIds() == 0u) ? nullptr :
@@ -69,6 +69,10 @@
   if (dex_file->NumStringIds() < num_strings) {
     num_strings = dex_file->NumStringIds();
   }
+  size_t num_types = mirror::DexCache::kDexCacheTypeCacheSize;
+  if (dex_file->NumTypeIds() < num_types) {
+    num_types = dex_file->NumTypeIds();
+  }
 
   // Note that we allocate the method type dex caches regardless of this flag,
   // and we make sure here that they're not used by the runtime. This is in the
@@ -108,8 +112,9 @@
       CHECK_EQ(strings[i].load(std::memory_order_relaxed).index, 0u);
       CHECK(strings[i].load(std::memory_order_relaxed).object.IsNull());
     }
-    for (size_t i = 0; i < dex_file->NumTypeIds(); ++i) {
-      CHECK(types[i].IsNull());
+    for (size_t i = 0; i < num_types; ++i) {
+      CHECK_EQ(types[i].load(std::memory_order_relaxed).index, 0u);
+      CHECK(types[i].load(std::memory_order_relaxed).object.IsNull());
     }
     for (size_t i = 0; i < dex_file->NumMethodIds(); ++i) {
       CHECK(mirror::DexCache::GetElementPtrSize(methods, i, image_pointer_size) == nullptr);
@@ -128,6 +133,9 @@
   if (strings != nullptr) {
     mirror::StringDexCachePair::Initialize(strings);
   }
+  if (types != nullptr) {
+    mirror::TypeDexCachePair::Initialize(types);
+  }
   if (method_types != nullptr) {
     mirror::MethodTypeDexCachePair::Initialize(method_types);
   }
@@ -136,7 +144,7 @@
                   strings,
                   num_strings,
                   types,
-                  dex_file->NumTypeIds(),
+                  num_types,
                   methods,
                   dex_file->NumMethodIds(),
                   fields,
@@ -152,7 +160,7 @@
                     ObjPtr<String> location,
                     StringDexCacheType* strings,
                     uint32_t num_strings,
-                    GcRoot<Class>* resolved_types,
+                    TypeDexCacheType* resolved_types,
                     uint32_t num_resolved_types,
                     ArtMethod** resolved_methods,
                     uint32_t num_resolved_methods,
diff --git a/runtime/mirror/dex_cache.h b/runtime/mirror/dex_cache.h
index 10bb5aa..0579198 100644
--- a/runtime/mirror/dex_cache.h
+++ b/runtime/mirror/dex_cache.h
@@ -18,14 +18,14 @@
 #define ART_RUNTIME_MIRROR_DEX_CACHE_H_
 
 #include "array.h"
-#include "art_field.h"
-#include "class.h"
+#include "base/bit_utils.h"
 #include "dex_file_types.h"
 #include "object.h"
 #include "object_array.h"
 
 namespace art {
 
+class ArtField;
 class ArtMethod;
 struct DexCacheOffsets;
 class DexFile;
@@ -37,6 +37,7 @@
 namespace mirror {
 
 class CallSite;
+class Class;
 class MethodType;
 class String;
 
@@ -61,7 +62,7 @@
   // it's always non-null if the id branch succeeds (except for the 0th id).
   // Set the initial state for the 0th entry to be {0,1} which is guaranteed to fail
   // the lookup id == stored id branch.
-  DexCachePair(T* object, uint32_t index)
+  DexCachePair(ObjPtr<T> object, uint32_t index)
       : object(object),
         index(index) {}
   DexCachePair() = default;
@@ -75,39 +76,28 @@
     dex_cache[0].store(first_elem, std::memory_order_relaxed);
   }
 
-  static GcRoot<T> Lookup(std::atomic<DexCachePair<T>>* dex_cache,
-                          uint32_t idx,
-                          uint32_t cache_size) {
-    DCHECK_NE(cache_size, 0u);
-    DexCachePair<T> element = dex_cache[idx % cache_size].load(std::memory_order_relaxed);
-    if (idx != element.index) {
-      return GcRoot<T>(nullptr);
-    }
-
-    DCHECK(!element.object.IsNull());
-    return element.object;
-  }
-
-  static void Assign(std::atomic<DexCachePair<T>>* dex_cache,
-                     uint32_t idx,
-                     T* object,
-                     uint32_t cache_size) {
-    DCHECK_LT(idx % cache_size, cache_size);
-    dex_cache[idx % cache_size].store(
-        DexCachePair<T>(object, idx), std::memory_order_relaxed);
-  }
-
   static uint32_t InvalidIndexForSlot(uint32_t slot) {
     // Since the cache size is a power of two, 0 will always map to slot 0.
     // Use 1 for slot 0 and 0 for all other slots.
     return (slot == 0) ? 1u : 0u;
   }
+
+  T* GetObjectForIndex(uint32_t idx) REQUIRES_SHARED(Locks::mutator_lock_) {
+    if (idx != index) {
+      return nullptr;
+    }
+    DCHECK(!object.IsNull());
+    return object.Read();
+  }
 };
 
-using StringDexCachePair = DexCachePair<mirror::String>;
+using TypeDexCachePair = DexCachePair<Class>;
+using TypeDexCacheType = std::atomic<TypeDexCachePair>;
+
+using StringDexCachePair = DexCachePair<String>;
 using StringDexCacheType = std::atomic<StringDexCachePair>;
 
-using MethodTypeDexCachePair = DexCachePair<mirror::MethodType>;
+using MethodTypeDexCachePair = DexCachePair<MethodType>;
 using MethodTypeDexCacheType = std::atomic<MethodTypeDexCachePair>;
 
 // C++ mirror of java.lang.DexCache.
@@ -116,6 +106,11 @@
   // Size of java.lang.DexCache.class.
   static uint32_t ClassSize(PointerSize pointer_size);
 
+  // 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),
+                "Type dex cache size is not a power of 2.");
+
   // Size of string dex cache. Needs to be a power of 2 for entrypoint assumptions to hold.
   static constexpr size_t kDexCacheStringCacheSize = 1024;
   static_assert(IsPowerOfTwo(kDexCacheStringCacheSize),
@@ -127,6 +122,10 @@
   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;
   }
@@ -157,7 +156,7 @@
       REQUIRES_SHARED(Locks::mutator_lock_);
 
   template <ReadBarrierOption kReadBarrierOption = kWithReadBarrier, typename Visitor>
-  void FixupResolvedTypes(GcRoot<mirror::Class>* dest, const Visitor& visitor)
+  void FixupResolvedTypes(TypeDexCacheType* dest, const Visitor& visitor)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
   template <ReadBarrierOption kReadBarrierOption = kWithReadBarrier, typename Visitor>
@@ -224,7 +223,7 @@
     return OFFSET_OF_OBJECT_MEMBER(DexCache, num_resolved_call_sites_);
   }
 
-  mirror::String* GetResolvedString(dex::StringIndex string_idx) ALWAYS_INLINE
+  String* GetResolvedString(dex::StringIndex string_idx) ALWAYS_INLINE
       REQUIRES_SHARED(Locks::mutator_lock_);
 
   void SetResolvedString(dex::StringIndex string_idx, ObjPtr<mirror::String> resolved) ALWAYS_INLINE
@@ -239,6 +238,8 @@
   void SetResolvedType(dex::TypeIndex type_idx, ObjPtr<Class> resolved)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
+  void ClearResolvedType(dex::TypeIndex type_idx) REQUIRES_SHARED(Locks::mutator_lock_);
+
   ALWAYS_INLINE ArtMethod* GetResolvedMethod(uint32_t method_idx, PointerSize ptr_size)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
@@ -278,11 +279,11 @@
     SetFieldPtr<false>(StringsOffset(), strings);
   }
 
-  GcRoot<Class>* GetResolvedTypes() ALWAYS_INLINE REQUIRES_SHARED(Locks::mutator_lock_) {
-    return GetFieldPtr<GcRoot<Class>*>(ResolvedTypesOffset());
+  TypeDexCacheType* GetResolvedTypes() ALWAYS_INLINE REQUIRES_SHARED(Locks::mutator_lock_) {
+    return GetFieldPtr<TypeDexCacheType*>(ResolvedTypesOffset());
   }
 
-  void SetResolvedTypes(GcRoot<Class>* resolved_types)
+  void SetResolvedTypes(TypeDexCacheType* resolved_types)
       ALWAYS_INLINE
       REQUIRES_SHARED(Locks::mutator_lock_) {
     SetFieldPtr<false>(ResolvedTypesOffset(), resolved_types);
@@ -363,7 +364,7 @@
     SetFieldPtr<false>(OFFSET_OF_OBJECT_MEMBER(DexCache, dex_file_), dex_file);
   }
 
-  void SetLocation(ObjPtr<mirror::String> location) REQUIRES_SHARED(Locks::mutator_lock_);
+  void SetLocation(ObjPtr<String> location) REQUIRES_SHARED(Locks::mutator_lock_);
 
   // NOTE: Get/SetElementPtrSize() are intended for working with ArtMethod** and ArtField**
   // provided by GetResolvedMethods/Fields() and ArtMethod::GetDexCacheResolvedMethods(),
@@ -380,7 +381,7 @@
             ObjPtr<String> location,
             StringDexCacheType* strings,
             uint32_t num_strings,
-            GcRoot<Class>* resolved_types,
+            TypeDexCacheType* resolved_types,
             uint32_t num_resolved_types,
             ArtMethod** resolved_methods,
             uint32_t num_resolved_methods,
@@ -393,12 +394,16 @@
             PointerSize pointer_size)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
+  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 MethodTypeSlotIndex(uint32_t proto_idx) REQUIRES_SHARED(Locks::mutator_lock_);
+
   // Visit instance fields of the dex cache as well as its associated arrays.
   template <bool kVisitNativeRoots,
             VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags,
             ReadBarrierOption kReadBarrierOption = kWithReadBarrier,
             typename Visitor>
-  void VisitReferences(ObjPtr<mirror::Class> klass, const Visitor& visitor)
+  void VisitReferences(ObjPtr<Class> klass, const Visitor& visitor)
       REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(Locks::heap_bitmap_lock_);
 
   HeapReference<Object> dex_;
@@ -410,7 +415,7 @@
   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_;         // GcRoot<Class>*, array with num_resolved_types_ elements.
+  uint64_t resolved_types_;         // TypeDexCacheType*, array with num_resolved_types_ elements.
   uint64_t strings_;                // std::atomic<StringDexCachePair>*, array with num_strings_
                                     // elements.
 
diff --git a/runtime/mirror/dex_cache_test.cc b/runtime/mirror/dex_cache_test.cc
index 5a2ab71..ef0aaaa 100644
--- a/runtime/mirror/dex_cache_test.cc
+++ b/runtime/mirror/dex_cache_test.cc
@@ -51,7 +51,8 @@
 
   EXPECT_TRUE(dex_cache->StaticStringSize() == dex_cache->NumStrings()
       || java_lang_dex_file_->NumStringIds() == dex_cache->NumStrings());
-  EXPECT_EQ(java_lang_dex_file_->NumTypeIds(),   dex_cache->NumResolvedTypes());
+  EXPECT_TRUE(dex_cache->StaticTypeSize() == dex_cache->NumResolvedTypes()
+      || java_lang_dex_file_->NumTypeIds() == dex_cache->NumResolvedTypes());
   EXPECT_EQ(java_lang_dex_file_->NumMethodIds(), dex_cache->NumResolvedMethods());
   EXPECT_EQ(java_lang_dex_file_->NumFieldIds(),  dex_cache->NumResolvedFields());
   EXPECT_TRUE(dex_cache->StaticMethodTypeSize() == dex_cache->NumResolvedMethodTypes()
diff --git a/runtime/mirror/object_test.cc b/runtime/mirror/object_test.cc
index e761e4d..d306f9c 100644
--- a/runtime/mirror/object_test.cc
+++ b/runtime/mirror/object_test.cc
@@ -726,57 +726,60 @@
   ScopedObjectAccess soa(Thread::Current());
   jobject jclass_loader = LoadDex("XandY");
   StackHandleScope<2> hs(soa.Self());
-  ObjPtr<mirror::Object, /*kPoison*/ true> null_ptr;
-  EXPECT_TRUE(null_ptr.IsNull());
-  EXPECT_TRUE(null_ptr.IsValid());
-  EXPECT_TRUE(null_ptr.Ptr() == nullptr);
-  EXPECT_TRUE(null_ptr == nullptr);
-  EXPECT_TRUE(null_ptr == null_ptr);
-  EXPECT_FALSE(null_ptr != null_ptr);
-  EXPECT_FALSE(null_ptr != nullptr);
-  null_ptr.AssertValid();
   Handle<ClassLoader> class_loader(hs.NewHandle(soa.Decode<ClassLoader>(jclass_loader)));
   Handle<mirror::Class> h_X(
       hs.NewHandle(class_linker_->FindClass(soa.Self(), "LX;", class_loader)));
-  ObjPtr<Class, /*kPoison*/ true> X(h_X.Get());
-  EXPECT_TRUE(!X.IsNull());
-  EXPECT_TRUE(X.IsValid());
-  EXPECT_TRUE(X.Ptr() != nullptr);
-  EXPECT_OBJ_PTR_EQ(h_X.Get(), X);
-  // FindClass may cause thread suspension, it should invalidate X.
-  ObjPtr<Class, /*kPoison*/ true> Y(class_linker_->FindClass(soa.Self(), "LY;", class_loader));
-  EXPECT_TRUE(!Y.IsNull());
-  EXPECT_TRUE(Y.IsValid());
-  EXPECT_TRUE(Y.Ptr() != nullptr);
 
-  // Should IsNull be safe to call on null ObjPtr? I'll allow it for now.
-  EXPECT_TRUE(!X.IsNull());
-  EXPECT_TRUE(!X.IsValid());
-  // Make X valid again by copying out of handle.
-  X.Assign(h_X.Get());
-  EXPECT_TRUE(!X.IsNull());
-  EXPECT_TRUE(X.IsValid());
-  EXPECT_OBJ_PTR_EQ(h_X.Get(), X);
+  if (kObjPtrPoisoning) {
+    ObjPtr<mirror::Object> null_ptr;
+    EXPECT_TRUE(null_ptr.IsNull());
+    EXPECT_TRUE(null_ptr.IsValid());
+    EXPECT_TRUE(null_ptr.Ptr() == nullptr);
+    EXPECT_TRUE(null_ptr == nullptr);
+    EXPECT_TRUE(null_ptr == null_ptr);
+    EXPECT_FALSE(null_ptr != null_ptr);
+    EXPECT_FALSE(null_ptr != nullptr);
+    null_ptr.AssertValid();
+    ObjPtr<Class> X(h_X.Get());
+    EXPECT_TRUE(!X.IsNull());
+    EXPECT_TRUE(X.IsValid());
+    EXPECT_TRUE(X.Ptr() != nullptr);
+    EXPECT_OBJ_PTR_EQ(h_X.Get(), X);
+    // FindClass may cause thread suspension, it should invalidate X.
+    ObjPtr<Class> Y(class_linker_->FindClass(soa.Self(), "LY;", class_loader));
+    EXPECT_TRUE(!Y.IsNull());
+    EXPECT_TRUE(Y.IsValid());
+    EXPECT_TRUE(Y.Ptr() != nullptr);
 
-  // Allow thread suspension to invalidate Y.
-  soa.Self()->AllowThreadSuspension();
-  EXPECT_TRUE(!Y.IsNull());
-  EXPECT_TRUE(!Y.IsValid());
+    // Should IsNull be safe to call on null ObjPtr? I'll allow it for now.
+    EXPECT_TRUE(!X.IsNull());
+    EXPECT_TRUE(!X.IsValid());
+    // Make X valid again by copying out of handle.
+    X.Assign(h_X.Get());
+    EXPECT_TRUE(!X.IsNull());
+    EXPECT_TRUE(X.IsValid());
+    EXPECT_OBJ_PTR_EQ(h_X.Get(), X);
 
-  // Test unpoisoned.
-  ObjPtr<mirror::Object, /*kPoison*/ false> unpoisoned;
-  EXPECT_TRUE(unpoisoned.IsNull());
-  EXPECT_TRUE(unpoisoned.IsValid());
-  EXPECT_TRUE(unpoisoned.Ptr() == nullptr);
-  EXPECT_TRUE(unpoisoned == nullptr);
-  EXPECT_TRUE(unpoisoned == unpoisoned);
-  EXPECT_FALSE(unpoisoned != unpoisoned);
-  EXPECT_FALSE(unpoisoned != nullptr);
+    // Allow thread suspension to invalidate Y.
+    soa.Self()->AllowThreadSuspension();
+    EXPECT_TRUE(!Y.IsNull());
+    EXPECT_TRUE(!Y.IsValid());
+  } else {
+    // Test unpoisoned.
+    ObjPtr<mirror::Object> unpoisoned;
+    EXPECT_TRUE(unpoisoned.IsNull());
+    EXPECT_TRUE(unpoisoned.IsValid());
+    EXPECT_TRUE(unpoisoned.Ptr() == nullptr);
+    EXPECT_TRUE(unpoisoned == nullptr);
+    EXPECT_TRUE(unpoisoned == unpoisoned);
+    EXPECT_FALSE(unpoisoned != unpoisoned);
+    EXPECT_FALSE(unpoisoned != nullptr);
 
-  unpoisoned = h_X.Get();
-  EXPECT_FALSE(unpoisoned.IsNull());
-  EXPECT_TRUE(unpoisoned == h_X.Get());
-  EXPECT_OBJ_PTR_EQ(unpoisoned, h_X.Get());
+    unpoisoned = h_X.Get();
+    EXPECT_FALSE(unpoisoned.IsNull());
+    EXPECT_TRUE(unpoisoned == h_X.Get());
+    EXPECT_OBJ_PTR_EQ(unpoisoned, h_X.Get());
+  }
 }
 
 }  // namespace mirror
diff --git a/runtime/mirror/string-inl.h b/runtime/mirror/string-inl.h
index c2407d7..57b20a1 100644
--- a/runtime/mirror/string-inl.h
+++ b/runtime/mirror/string-inl.h
@@ -36,7 +36,7 @@
 namespace mirror {
 
 inline uint32_t String::ClassSize(PointerSize pointer_size) {
-  uint32_t vtable_entries = Object::kVTableLength + 57;
+  uint32_t vtable_entries = Object::kVTableLength + 56;
   return Class::ComputeClassSize(true, vtable_entries, 0, 0, 0, 1, 2, pointer_size);
 }
 
@@ -311,9 +311,7 @@
 inline bool String::AllASCII(const MemoryType* chars, const int length) {
   static_assert(std::is_unsigned<MemoryType>::value, "Expecting unsigned MemoryType");
   for (int i = 0; i < length; ++i) {
-    // Valid ASCII characters are in range 1..0x7f. Zero is not considered ASCII
-    // because it would complicate the detection of ASCII strings in Modified-UTF8.
-    if ((chars[i] - 1u) >= 0x7fu) {
+    if (!IsASCII(chars[i])) {
       return false;
     }
   }
diff --git a/runtime/mirror/string.cc b/runtime/mirror/string.cc
index 0ab0bd6..884b88a 100644
--- a/runtime/mirror/string.cc
+++ b/runtime/mirror/string.cc
@@ -79,14 +79,55 @@
   }
 }
 
-void String::SetCharAt(int32_t index, uint16_t c) {
-  DCHECK((index >= 0) && (index < GetLength()));
-  if (IsCompressed()) {
-    // TODO: Handle the case where String is compressed and c is non-ASCII
-    GetValueCompressed()[index] = static_cast<uint8_t>(c);
-  } else {
-    GetValue()[index] = c;
+inline bool String::AllASCIIExcept(const uint16_t* chars, int32_t length, uint16_t non_ascii) {
+  DCHECK(!IsASCII(non_ascii));
+  for (int32_t i = 0; i < length; ++i) {
+    if (!IsASCII(chars[i]) && chars[i] != non_ascii) {
+      return false;
+    }
   }
+  return true;
+}
+
+ObjPtr<String> String::DoReplace(Thread* self, uint16_t old_c, uint16_t new_c) {
+  DCHECK(IsCompressed() ? ContainsElement(ArrayRef<uint8_t>(value_compressed_, GetLength()), old_c)
+                        : ContainsElement(ArrayRef<uint16_t>(value_, GetLength()), old_c));
+  int32_t length = GetLength();
+  bool compressible =
+      kUseStringCompression &&
+      IsASCII(new_c) &&
+      (IsCompressed() || (!IsASCII(old_c) && AllASCIIExcept(value_, length, old_c)));
+  gc::AllocatorType allocator_type = Runtime::Current()->GetHeap()->GetCurrentAllocator();
+  const int32_t length_with_flag = String::GetFlaggedCount(GetLength(), compressible);
+  SetStringCountVisitor visitor(length_with_flag);
+  ObjPtr<String> string = Alloc<true>(self, length_with_flag, allocator_type, visitor);
+  if (UNLIKELY(string == nullptr)) {
+    return nullptr;
+  }
+  if (compressible) {
+    auto replace = [old_c, new_c](uint16_t c) {
+      return dchecked_integral_cast<uint8_t>((old_c != c) ? c : new_c);
+    };
+    uint8_t* out = string->value_compressed_;
+    if (LIKELY(IsCompressed())) {  // LIKELY(compressible == IsCompressed())
+      std::transform(value_compressed_, value_compressed_ + length, out, replace);
+    } else {
+      std::transform(value_, value_ + length, out, replace);
+    }
+    DCHECK(kUseStringCompression && AllASCII(out, length));
+  } else {
+    auto replace = [old_c, new_c](uint16_t c) {
+      return (old_c != c) ? c : new_c;
+    };
+    uint16_t* out = string->value_;
+    if (UNLIKELY(IsCompressed())) {  // LIKELY(compressible == IsCompressed())
+      std::transform(value_compressed_, value_compressed_ + length, out, replace);
+    } else {
+      std::transform(value_, value_ + length, out, replace);
+    }
+    DCHECK(!kUseStringCompression || !AllASCII(out, length));
+  }
+  return string;
 }
 
 String* String::AllocFromStrings(Thread* self, Handle<String> string, Handle<String> string2) {
diff --git a/runtime/mirror/string.h b/runtime/mirror/string.h
index 38f6dd4..35ce98e 100644
--- a/runtime/mirror/string.h
+++ b/runtime/mirror/string.h
@@ -94,7 +94,10 @@
 
   uint16_t CharAt(int32_t index) REQUIRES_SHARED(Locks::mutator_lock_);
 
-  void SetCharAt(int32_t index, uint16_t c) REQUIRES_SHARED(Locks::mutator_lock_);
+  // Create a new string where all occurences of `old_c` are replaced with `new_c`.
+  // String.doReplace(char, char) is called from String.replace(char, char) when there is a match.
+  ObjPtr<String> DoReplace(Thread* self, uint16_t old_c, uint16_t new_c)
+      REQUIRES_SHARED(Locks::mutator_lock_);
 
   ObjPtr<String> Intern() REQUIRES_SHARED(Locks::mutator_lock_);
 
@@ -229,6 +232,14 @@
       REQUIRES_SHARED(Locks::mutator_lock_);
 
  private:
+  static constexpr bool IsASCII(uint16_t c) {
+    // Valid ASCII characters are in range 1..0x7f. Zero is not considered ASCII
+    // because it would complicate the detection of ASCII strings in Modified-UTF8.
+    return (c - 1u) < 0x7fu;
+  }
+
+  static bool AllASCIIExcept(const uint16_t* chars, int32_t length, uint16_t non_ascii);
+
   void SetHashCode(int32_t new_hash_code) REQUIRES_SHARED(Locks::mutator_lock_) {
     // Hash code is invariant so use non-transactional mode. Also disable check as we may run inside
     // a transaction.
diff --git a/runtime/modifiers.h b/runtime/modifiers.h
index ae6b31d..461f870 100644
--- a/runtime/modifiers.h
+++ b/runtime/modifiers.h
@@ -45,6 +45,9 @@
 static constexpr uint32_t kAccConstructor =           0x00010000;  // method (dex only) <(cl)init>
 static constexpr uint32_t kAccDeclaredSynchronized =  0x00020000;  // method (dex only)
 static constexpr uint32_t kAccClassIsProxy =          0x00040000;  // class  (dex only)
+// Set to indicate that the ArtMethod is obsolete and has a different DexCache + DexFile from its
+// declaring class. This flag may only be applied to methods.
+static constexpr uint32_t kAccObsoleteMethod =        0x00040000;  // method (runtime)
 // Used by a method to denote that its execution does not need to go through slow path interpreter.
 static constexpr uint32_t kAccSkipAccessChecks =      0x00080000;  // method (dex only)
 // Used by a class to denote that the verifier has attempted to check it at least once.
@@ -67,10 +70,6 @@
 
 // Set by the verifier for a method that could not be verified to follow structured locking.
 static constexpr uint32_t kAccMustCountLocks =        0x02000000;  // method (runtime)
-// Set to indicate that the ArtMethod is obsolete and has a different DexCache from its declaring
-// class.
-// TODO Might want to re-arrange some of these so that we can have obsolete + intrinsic methods.
-static constexpr uint32_t kAccObsoleteMethod =        0x04000000;  // method (runtime)
 
 // Set by the class linker for a method that has only one implementation for a
 // virtual call.
diff --git a/runtime/native/dalvik_system_VMDebug.cc b/runtime/native/dalvik_system_VMDebug.cc
index 0d24587..f6a73a8 100644
--- a/runtime/native/dalvik_system_VMDebug.cc
+++ b/runtime/native/dalvik_system_VMDebug.cc
@@ -537,14 +537,14 @@
   NATIVE_METHOD(VMDebug, getAllocCount, "(I)I"),
   NATIVE_METHOD(VMDebug, getHeapSpaceStats, "([J)V"),
   NATIVE_METHOD(VMDebug, getInstructionCount, "([I)V"),
-  NATIVE_METHOD(VMDebug, getLoadedClassCount, "!()I"),
+  FAST_NATIVE_METHOD(VMDebug, getLoadedClassCount, "()I"),
   NATIVE_METHOD(VMDebug, getVmFeatureList, "()[Ljava/lang/String;"),
   NATIVE_METHOD(VMDebug, infopoint, "(I)V"),
-  NATIVE_METHOD(VMDebug, isDebuggerConnected, "!()Z"),
-  NATIVE_METHOD(VMDebug, isDebuggingEnabled, "!()Z"),
+  FAST_NATIVE_METHOD(VMDebug, isDebuggerConnected, "()Z"),
+  FAST_NATIVE_METHOD(VMDebug, isDebuggingEnabled, "()Z"),
   NATIVE_METHOD(VMDebug, getMethodTracingMode, "()I"),
-  NATIVE_METHOD(VMDebug, lastDebuggerActivity, "!()J"),
-  NATIVE_METHOD(VMDebug, printLoadedClasses, "!(I)V"),
+  FAST_NATIVE_METHOD(VMDebug, lastDebuggerActivity, "()J"),
+  FAST_NATIVE_METHOD(VMDebug, printLoadedClasses, "(I)V"),
   NATIVE_METHOD(VMDebug, resetAllocCount, "(I)V"),
   NATIVE_METHOD(VMDebug, resetInstructionCount, "()V"),
   NATIVE_METHOD(VMDebug, startAllocCounting, "()V"),
@@ -557,7 +557,7 @@
   NATIVE_METHOD(VMDebug, stopEmulatorTracing, "()V"),
   NATIVE_METHOD(VMDebug, stopInstructionCounting, "()V"),
   NATIVE_METHOD(VMDebug, stopMethodTracing, "()V"),
-  NATIVE_METHOD(VMDebug, threadCpuTimeNanos, "!()J"),
+  FAST_NATIVE_METHOD(VMDebug, threadCpuTimeNanos, "()J"),
   NATIVE_METHOD(VMDebug, getRuntimeStatInternal, "(I)Ljava/lang/String;"),
   NATIVE_METHOD(VMDebug, getRuntimeStatsInternal, "()[Ljava/lang/String;"),
   NATIVE_METHOD(VMDebug, attachAgent, "(Ljava/lang/String;)V"),
diff --git a/runtime/native/dalvik_system_VMRuntime.cc b/runtime/native/dalvik_system_VMRuntime.cc
index 6bfccdc..efc42fd 100644
--- a/runtime/native/dalvik_system_VMRuntime.cc
+++ b/runtime/native/dalvik_system_VMRuntime.cc
@@ -642,7 +642,7 @@
 }
 
 static JNINativeMethod gMethods[] = {
-  NATIVE_METHOD(VMRuntime, addressOf, "!(Ljava/lang/Object;)J"),
+  FAST_NATIVE_METHOD(VMRuntime, addressOf, "(Ljava/lang/Object;)J"),
   NATIVE_METHOD(VMRuntime, bootClassPath, "()Ljava/lang/String;"),
   NATIVE_METHOD(VMRuntime, clampGrowthLimit, "()V"),
   NATIVE_METHOD(VMRuntime, classPath, "()Ljava/lang/String;"),
@@ -650,11 +650,11 @@
   NATIVE_METHOD(VMRuntime, concurrentGC, "()V"),
   NATIVE_METHOD(VMRuntime, disableJitCompilation, "()V"),
   NATIVE_METHOD(VMRuntime, getTargetHeapUtilization, "()F"),
-  NATIVE_METHOD(VMRuntime, isDebuggerActive, "!()Z"),
-  NATIVE_METHOD(VMRuntime, isNativeDebuggable, "!()Z"),
+  FAST_NATIVE_METHOD(VMRuntime, isDebuggerActive, "()Z"),
+  FAST_NATIVE_METHOD(VMRuntime, isNativeDebuggable, "()Z"),
   NATIVE_METHOD(VMRuntime, nativeSetTargetHeapUtilization, "(F)V"),
-  NATIVE_METHOD(VMRuntime, newNonMovableArray, "!(Ljava/lang/Class;I)Ljava/lang/Object;"),
-  NATIVE_METHOD(VMRuntime, newUnpaddedArray, "!(Ljava/lang/Class;I)Ljava/lang/Object;"),
+  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, setTargetSdkVersionNative, "(I)V"),
   NATIVE_METHOD(VMRuntime, registerNativeAllocation, "(I)V"),
@@ -671,8 +671,8 @@
   NATIVE_METHOD(VMRuntime, vmVersion, "()Ljava/lang/String;"),
   NATIVE_METHOD(VMRuntime, vmLibrary, "()Ljava/lang/String;"),
   NATIVE_METHOD(VMRuntime, vmInstructionSet, "()Ljava/lang/String;"),
-  NATIVE_METHOD(VMRuntime, is64Bit, "!()Z"),
-  NATIVE_METHOD(VMRuntime, isCheckJniEnabled, "!()Z"),
+  FAST_NATIVE_METHOD(VMRuntime, is64Bit, "()Z"),
+  FAST_NATIVE_METHOD(VMRuntime, isCheckJniEnabled, "()Z"),
   NATIVE_METHOD(VMRuntime, preloadDexCaches, "()V"),
   NATIVE_METHOD(VMRuntime, registerAppInfo,
                 "(Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;)V"),
diff --git a/runtime/native/dalvik_system_VMStack.cc b/runtime/native/dalvik_system_VMStack.cc
index be6f7f2..0dfafa4 100644
--- a/runtime/native/dalvik_system_VMStack.cc
+++ b/runtime/native/dalvik_system_VMStack.cc
@@ -139,11 +139,11 @@
 }
 
 static JNINativeMethod gMethods[] = {
-  NATIVE_METHOD(VMStack, fillStackTraceElements, "!(Ljava/lang/Thread;[Ljava/lang/StackTraceElement;)I"),
-  NATIVE_METHOD(VMStack, getCallingClassLoader, "!()Ljava/lang/ClassLoader;"),
-  NATIVE_METHOD(VMStack, getClosestUserClassLoader, "!()Ljava/lang/ClassLoader;"),
-  NATIVE_METHOD(VMStack, getStackClass2, "!()Ljava/lang/Class;"),
-  NATIVE_METHOD(VMStack, getThreadStackTrace, "!(Ljava/lang/Thread;)[Ljava/lang/StackTraceElement;"),
+  FAST_NATIVE_METHOD(VMStack, fillStackTraceElements, "(Ljava/lang/Thread;[Ljava/lang/StackTraceElement;)I"),
+  FAST_NATIVE_METHOD(VMStack, getCallingClassLoader, "()Ljava/lang/ClassLoader;"),
+  FAST_NATIVE_METHOD(VMStack, getClosestUserClassLoader, "()Ljava/lang/ClassLoader;"),
+  FAST_NATIVE_METHOD(VMStack, getStackClass2, "()Ljava/lang/Class;"),
+  FAST_NATIVE_METHOD(VMStack, getThreadStackTrace, "(Ljava/lang/Thread;)[Ljava/lang/StackTraceElement;"),
 };
 
 void register_dalvik_system_VMStack(JNIEnv* env) {
diff --git a/runtime/native/java_lang_Class.cc b/runtime/native/java_lang_Class.cc
index 256787b..c8431c0 100644
--- a/runtime/native/java_lang_Class.cc
+++ b/runtime/native/java_lang_Class.cc
@@ -713,36 +713,36 @@
 }
 
 static JNINativeMethod gMethods[] = {
-  NATIVE_METHOD(Class, classForName,
-                "!(Ljava/lang/String;ZLjava/lang/ClassLoader;)Ljava/lang/Class;"),
-  NATIVE_METHOD(Class, getDeclaredAnnotation,
-                "!(Ljava/lang/Class;)Ljava/lang/annotation/Annotation;"),
-  NATIVE_METHOD(Class, getDeclaredAnnotations, "!()[Ljava/lang/annotation/Annotation;"),
-  NATIVE_METHOD(Class, getDeclaredClasses, "!()[Ljava/lang/Class;"),
-  NATIVE_METHOD(Class, getDeclaredConstructorInternal,
-                "!([Ljava/lang/Class;)Ljava/lang/reflect/Constructor;"),
-  NATIVE_METHOD(Class, getDeclaredConstructorsInternal, "!(Z)[Ljava/lang/reflect/Constructor;"),
-  NATIVE_METHOD(Class, getDeclaredField, "!(Ljava/lang/String;)Ljava/lang/reflect/Field;"),
-  NATIVE_METHOD(Class, getPublicFieldRecursive, "!(Ljava/lang/String;)Ljava/lang/reflect/Field;"),
-  NATIVE_METHOD(Class, getDeclaredFields, "!()[Ljava/lang/reflect/Field;"),
-  NATIVE_METHOD(Class, getDeclaredFieldsUnchecked, "!(Z)[Ljava/lang/reflect/Field;"),
-  NATIVE_METHOD(Class, getDeclaredMethodInternal,
-                "!(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;"),
-  NATIVE_METHOD(Class, getDeclaredMethodsUnchecked,
-                "!(Z)[Ljava/lang/reflect/Method;"),
-  NATIVE_METHOD(Class, getDeclaringClass, "!()Ljava/lang/Class;"),
-  NATIVE_METHOD(Class, getEnclosingClass, "!()Ljava/lang/Class;"),
-  NATIVE_METHOD(Class, getEnclosingConstructorNative, "!()Ljava/lang/reflect/Constructor;"),
-  NATIVE_METHOD(Class, getEnclosingMethodNative, "!()Ljava/lang/reflect/Method;"),
-  NATIVE_METHOD(Class, getInnerClassFlags, "!(I)I"),
-  NATIVE_METHOD(Class, getInnerClassName, "!()Ljava/lang/String;"),
-  NATIVE_METHOD(Class, getNameNative, "!()Ljava/lang/String;"),
-  NATIVE_METHOD(Class, getProxyInterfaces, "!()[Ljava/lang/Class;"),
-  NATIVE_METHOD(Class, getPublicDeclaredFields, "!()[Ljava/lang/reflect/Field;"),
-  NATIVE_METHOD(Class, getSignatureAnnotation, "!()[Ljava/lang/String;"),
-  NATIVE_METHOD(Class, isAnonymousClass, "!()Z"),
-  NATIVE_METHOD(Class, isDeclaredAnnotationPresent, "!(Ljava/lang/Class;)Z"),
-  NATIVE_METHOD(Class, newInstance, "!()Ljava/lang/Object;"),
+  FAST_NATIVE_METHOD(Class, classForName,
+                "(Ljava/lang/String;ZLjava/lang/ClassLoader;)Ljava/lang/Class;"),
+  FAST_NATIVE_METHOD(Class, getDeclaredAnnotation,
+                "(Ljava/lang/Class;)Ljava/lang/annotation/Annotation;"),
+  FAST_NATIVE_METHOD(Class, getDeclaredAnnotations, "()[Ljava/lang/annotation/Annotation;"),
+  FAST_NATIVE_METHOD(Class, getDeclaredClasses, "()[Ljava/lang/Class;"),
+  FAST_NATIVE_METHOD(Class, getDeclaredConstructorInternal,
+                "([Ljava/lang/Class;)Ljava/lang/reflect/Constructor;"),
+  FAST_NATIVE_METHOD(Class, getDeclaredConstructorsInternal, "(Z)[Ljava/lang/reflect/Constructor;"),
+  FAST_NATIVE_METHOD(Class, getDeclaredField, "(Ljava/lang/String;)Ljava/lang/reflect/Field;"),
+  FAST_NATIVE_METHOD(Class, getPublicFieldRecursive, "(Ljava/lang/String;)Ljava/lang/reflect/Field;"),
+  FAST_NATIVE_METHOD(Class, getDeclaredFields, "()[Ljava/lang/reflect/Field;"),
+  FAST_NATIVE_METHOD(Class, getDeclaredFieldsUnchecked, "(Z)[Ljava/lang/reflect/Field;"),
+  FAST_NATIVE_METHOD(Class, getDeclaredMethodInternal,
+                "(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;"),
+  FAST_NATIVE_METHOD(Class, getDeclaredMethodsUnchecked,
+                "(Z)[Ljava/lang/reflect/Method;"),
+  FAST_NATIVE_METHOD(Class, getDeclaringClass, "()Ljava/lang/Class;"),
+  FAST_NATIVE_METHOD(Class, getEnclosingClass, "()Ljava/lang/Class;"),
+  FAST_NATIVE_METHOD(Class, getEnclosingConstructorNative, "()Ljava/lang/reflect/Constructor;"),
+  FAST_NATIVE_METHOD(Class, getEnclosingMethodNative, "()Ljava/lang/reflect/Method;"),
+  FAST_NATIVE_METHOD(Class, getInnerClassFlags, "(I)I"),
+  FAST_NATIVE_METHOD(Class, getInnerClassName, "()Ljava/lang/String;"),
+  FAST_NATIVE_METHOD(Class, getNameNative, "()Ljava/lang/String;"),
+  FAST_NATIVE_METHOD(Class, getProxyInterfaces, "()[Ljava/lang/Class;"),
+  FAST_NATIVE_METHOD(Class, getPublicDeclaredFields, "()[Ljava/lang/reflect/Field;"),
+  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, newInstance, "()Ljava/lang/Object;"),
 };
 
 void register_java_lang_Class(JNIEnv* env) {
diff --git a/runtime/native/java_lang_DexCache.cc b/runtime/native/java_lang_DexCache.cc
index b1ed74a..8fda4df 100644
--- a/runtime/native/java_lang_DexCache.cc
+++ b/runtime/native/java_lang_DexCache.cc
@@ -53,7 +53,7 @@
 static jobject DexCache_getResolvedType(JNIEnv* env, jobject javaDexCache, jint type_index) {
   ScopedFastNativeObjectAccess soa(env);
   ObjPtr<mirror::DexCache> dex_cache = soa.Decode<mirror::DexCache>(javaDexCache);
-  CHECK_LT(static_cast<size_t>(type_index), dex_cache->NumResolvedTypes());
+  CHECK_LT(static_cast<size_t>(type_index), dex_cache->GetDexFile()->NumTypeIds());
   return soa.AddLocalReference<jobject>(dex_cache->GetResolvedType(dex::TypeIndex(type_index)));
 }
 
@@ -95,11 +95,11 @@
 }
 
 static JNINativeMethod gMethods[] = {
-  NATIVE_METHOD(DexCache, getDexNative, "!()Lcom/android/dex/Dex;"),
-  NATIVE_METHOD(DexCache, getResolvedType, "!(I)Ljava/lang/Class;"),
-  NATIVE_METHOD(DexCache, getResolvedString, "!(I)Ljava/lang/String;"),
-  NATIVE_METHOD(DexCache, setResolvedType, "!(ILjava/lang/Class;)V"),
-  NATIVE_METHOD(DexCache, setResolvedString, "!(ILjava/lang/String;)V"),
+  FAST_NATIVE_METHOD(DexCache, getDexNative, "()Lcom/android/dex/Dex;"),
+  FAST_NATIVE_METHOD(DexCache, getResolvedType, "(I)Ljava/lang/Class;"),
+  FAST_NATIVE_METHOD(DexCache, getResolvedString, "(I)Ljava/lang/String;"),
+  FAST_NATIVE_METHOD(DexCache, setResolvedType, "(ILjava/lang/Class;)V"),
+  FAST_NATIVE_METHOD(DexCache, setResolvedString, "(ILjava/lang/String;)V"),
 };
 
 void register_java_lang_DexCache(JNIEnv* env) {
diff --git a/runtime/native/java_lang_Object.cc b/runtime/native/java_lang_Object.cc
index 6493865..6989244 100644
--- a/runtime/native/java_lang_Object.cc
+++ b/runtime/native/java_lang_Object.cc
@@ -20,7 +20,6 @@
 #include "mirror/object-inl.h"
 #include "scoped_fast_native_object_access-inl.h"
 
-
 namespace art {
 
 static jobject Object_internalClone(JNIEnv* env, jobject java_this) {
@@ -50,11 +49,11 @@
 }
 
 static JNINativeMethod gMethods[] = {
-  NATIVE_METHOD(Object, internalClone, "!()Ljava/lang/Object;"),
-  NATIVE_METHOD(Object, notify, "!()V"),
-  NATIVE_METHOD(Object, notifyAll, "!()V"),
-  OVERLOADED_NATIVE_METHOD(Object, wait, "!()V", wait),
-  OVERLOADED_NATIVE_METHOD(Object, wait, "!(JI)V", waitJI),
+  FAST_NATIVE_METHOD(Object, internalClone, "()Ljava/lang/Object;"),
+  FAST_NATIVE_METHOD(Object, notify, "()V"),
+  FAST_NATIVE_METHOD(Object, notifyAll, "()V"),
+  OVERLOADED_FAST_NATIVE_METHOD(Object, wait, "()V", wait),
+  OVERLOADED_FAST_NATIVE_METHOD(Object, wait, "(JI)V", waitJI),
 };
 
 void register_java_lang_Object(JNIEnv* env) {
diff --git a/runtime/native/java_lang_String.cc b/runtime/native/java_lang_String.cc
index f1d6ff5..2e561ff 100644
--- a/runtime/native/java_lang_String.cc
+++ b/runtime/native/java_lang_String.cc
@@ -99,9 +99,11 @@
   return soa.AddLocalReference<jstring>(result);
 }
 
-static void String_setCharAt(JNIEnv* env, jobject java_this, jint index, jchar c) {
+static jstring String_doReplace(JNIEnv* env, jobject java_this, jchar old_c, jchar new_c) {
   ScopedFastNativeObjectAccess soa(env);
-  soa.Decode<mirror::String>(java_this)->SetCharAt(index, c);
+  ObjPtr<mirror::String> result =
+      soa.Decode<mirror::String>(java_this)->DoReplace(soa.Self(), old_c, new_c);
+  return soa.AddLocalReference<jstring>(result);
 }
 
 static jcharArray String_toCharArray(JNIEnv* env, jobject java_this) {
@@ -111,15 +113,15 @@
 }
 
 static JNINativeMethod gMethods[] = {
-  NATIVE_METHOD(String, charAt, "!(I)C"),
-  NATIVE_METHOD(String, compareTo, "!(Ljava/lang/String;)I"),
-  NATIVE_METHOD(String, concat, "!(Ljava/lang/String;)Ljava/lang/String;"),
-  NATIVE_METHOD(String, fastIndexOf, "!(II)I"),
-  NATIVE_METHOD(String, fastSubstring, "!(II)Ljava/lang/String;"),
-  NATIVE_METHOD(String, getCharsNoCheck, "!(II[CI)V"),
-  NATIVE_METHOD(String, intern, "!()Ljava/lang/String;"),
-  NATIVE_METHOD(String, setCharAt, "!(IC)V"),
-  NATIVE_METHOD(String, toCharArray, "!()[C"),
+  FAST_NATIVE_METHOD(String, charAt, "(I)C"),
+  FAST_NATIVE_METHOD(String, compareTo, "(Ljava/lang/String;)I"),
+  FAST_NATIVE_METHOD(String, concat, "(Ljava/lang/String;)Ljava/lang/String;"),
+  FAST_NATIVE_METHOD(String, doReplace, "(CC)Ljava/lang/String;"),
+  FAST_NATIVE_METHOD(String, fastIndexOf, "(II)I"),
+  FAST_NATIVE_METHOD(String, fastSubstring, "(II)Ljava/lang/String;"),
+  FAST_NATIVE_METHOD(String, getCharsNoCheck, "(II[CI)V"),
+  FAST_NATIVE_METHOD(String, intern, "()Ljava/lang/String;"),
+  FAST_NATIVE_METHOD(String, toCharArray, "()[C"),
 };
 
 void register_java_lang_String(JNIEnv* env) {
diff --git a/runtime/native/java_lang_StringFactory.cc b/runtime/native/java_lang_StringFactory.cc
index e0738a4..ec3c7c2 100644
--- a/runtime/native/java_lang_StringFactory.cc
+++ b/runtime/native/java_lang_StringFactory.cc
@@ -87,9 +87,9 @@
 }
 
 static JNINativeMethod gMethods[] = {
-  NATIVE_METHOD(StringFactory, newStringFromBytes, "!([BIII)Ljava/lang/String;"),
-  NATIVE_METHOD(StringFactory, newStringFromChars, "!(II[C)Ljava/lang/String;"),
-  NATIVE_METHOD(StringFactory, newStringFromString, "!(Ljava/lang/String;)Ljava/lang/String;"),
+  FAST_NATIVE_METHOD(StringFactory, newStringFromBytes, "([BIII)Ljava/lang/String;"),
+  FAST_NATIVE_METHOD(StringFactory, newStringFromChars, "(II[C)Ljava/lang/String;"),
+  FAST_NATIVE_METHOD(StringFactory, newStringFromString, "(Ljava/lang/String;)Ljava/lang/String;"),
 };
 
 void register_java_lang_StringFactory(JNIEnv* env) {
diff --git a/runtime/native/java_lang_System.cc b/runtime/native/java_lang_System.cc
index 7f8da80..d7c9cd0 100644
--- a/runtime/native/java_lang_System.cc
+++ b/runtime/native/java_lang_System.cc
@@ -237,16 +237,16 @@
 }
 
 static JNINativeMethod gMethods[] = {
-  NATIVE_METHOD(System, arraycopy, "!(Ljava/lang/Object;ILjava/lang/Object;II)V"),
-  NATIVE_METHOD(System, arraycopyCharUnchecked, "!([CI[CII)V"),
-  NATIVE_METHOD(System, arraycopyByteUnchecked, "!([BI[BII)V"),
-  NATIVE_METHOD(System, arraycopyShortUnchecked, "!([SI[SII)V"),
-  NATIVE_METHOD(System, arraycopyIntUnchecked, "!([II[III)V"),
-  NATIVE_METHOD(System, arraycopyLongUnchecked, "!([JI[JII)V"),
-  NATIVE_METHOD(System, arraycopyFloatUnchecked, "!([FI[FII)V"),
-  NATIVE_METHOD(System, arraycopyDoubleUnchecked, "!([DI[DII)V"),
-  NATIVE_METHOD(System, arraycopyBooleanUnchecked, "!([ZI[ZII)V"),
-  NATIVE_METHOD(System, identityHashCode, "!(Ljava/lang/Object;)I"),
+  FAST_NATIVE_METHOD(System, arraycopy, "(Ljava/lang/Object;ILjava/lang/Object;II)V"),
+  FAST_NATIVE_METHOD(System, arraycopyCharUnchecked, "([CI[CII)V"),
+  FAST_NATIVE_METHOD(System, arraycopyByteUnchecked, "([BI[BII)V"),
+  FAST_NATIVE_METHOD(System, arraycopyShortUnchecked, "([SI[SII)V"),
+  FAST_NATIVE_METHOD(System, arraycopyIntUnchecked, "([II[III)V"),
+  FAST_NATIVE_METHOD(System, arraycopyLongUnchecked, "([JI[JII)V"),
+  FAST_NATIVE_METHOD(System, arraycopyFloatUnchecked, "([FI[FII)V"),
+  FAST_NATIVE_METHOD(System, arraycopyDoubleUnchecked, "([DI[DII)V"),
+  FAST_NATIVE_METHOD(System, arraycopyBooleanUnchecked, "([ZI[ZII)V"),
+  FAST_NATIVE_METHOD(System, identityHashCode, "(Ljava/lang/Object;)I"),
 };
 
 void register_java_lang_System(JNIEnv* env) {
diff --git a/runtime/native/java_lang_Thread.cc b/runtime/native/java_lang_Thread.cc
index 195091f..346bd30 100644
--- a/runtime/native/java_lang_Thread.cc
+++ b/runtime/native/java_lang_Thread.cc
@@ -187,16 +187,16 @@
 }
 
 static JNINativeMethod gMethods[] = {
-  NATIVE_METHOD(Thread, currentThread, "!()Ljava/lang/Thread;"),
-  NATIVE_METHOD(Thread, interrupted, "!()Z"),
-  NATIVE_METHOD(Thread, isInterrupted, "!()Z"),
+  FAST_NATIVE_METHOD(Thread, currentThread, "()Ljava/lang/Thread;"),
+  FAST_NATIVE_METHOD(Thread, interrupted, "()Z"),
+  FAST_NATIVE_METHOD(Thread, isInterrupted, "()Z"),
   NATIVE_METHOD(Thread, nativeCreate, "(Ljava/lang/Thread;JZ)V"),
   NATIVE_METHOD(Thread, nativeGetStatus, "(Z)I"),
   NATIVE_METHOD(Thread, nativeHoldsLock, "(Ljava/lang/Object;)Z"),
-  NATIVE_METHOD(Thread, nativeInterrupt, "!()V"),
+  FAST_NATIVE_METHOD(Thread, nativeInterrupt, "()V"),
   NATIVE_METHOD(Thread, nativeSetName, "(Ljava/lang/String;)V"),
   NATIVE_METHOD(Thread, nativeSetPriority, "(I)V"),
-  NATIVE_METHOD(Thread, sleep, "!(Ljava/lang/Object;JI)V"),
+  FAST_NATIVE_METHOD(Thread, sleep, "(Ljava/lang/Object;JI)V"),
   NATIVE_METHOD(Thread, yield, "()V"),
 };
 
diff --git a/runtime/native/java_lang_Throwable.cc b/runtime/native/java_lang_Throwable.cc
index ff3e044..654b8a8 100644
--- a/runtime/native/java_lang_Throwable.cc
+++ b/runtime/native/java_lang_Throwable.cc
@@ -36,8 +36,8 @@
 }
 
 static JNINativeMethod gMethods[] = {
-  NATIVE_METHOD(Throwable, nativeFillInStackTrace, "!()Ljava/lang/Object;"),
-  NATIVE_METHOD(Throwable, nativeGetStackTrace, "!(Ljava/lang/Object;)[Ljava/lang/StackTraceElement;"),
+  FAST_NATIVE_METHOD(Throwable, nativeFillInStackTrace, "()Ljava/lang/Object;"),
+  FAST_NATIVE_METHOD(Throwable, nativeGetStackTrace, "(Ljava/lang/Object;)[Ljava/lang/StackTraceElement;"),
 };
 
 void register_java_lang_Throwable(JNIEnv* env) {
diff --git a/runtime/native/java_lang_VMClassLoader.cc b/runtime/native/java_lang_VMClassLoader.cc
index a8fa7db..54ab861 100644
--- a/runtime/native/java_lang_VMClassLoader.cc
+++ b/runtime/native/java_lang_VMClassLoader.cc
@@ -136,7 +136,7 @@
 }
 
 static JNINativeMethod gMethods[] = {
-  NATIVE_METHOD(VMClassLoader, findLoadedClass, "!(Ljava/lang/ClassLoader;Ljava/lang/String;)Ljava/lang/Class;"),
+  FAST_NATIVE_METHOD(VMClassLoader, findLoadedClass, "(Ljava/lang/ClassLoader;Ljava/lang/String;)Ljava/lang/Class;"),
   NATIVE_METHOD(VMClassLoader, getBootClassPathEntries, "()[Ljava/lang/String;"),
 };
 
diff --git a/runtime/native/java_lang_ref_FinalizerReference.cc b/runtime/native/java_lang_ref_FinalizerReference.cc
index ecafd0e..afedc5e 100644
--- a/runtime/native/java_lang_ref_FinalizerReference.cc
+++ b/runtime/native/java_lang_ref_FinalizerReference.cc
@@ -40,8 +40,8 @@
 }
 
 static JNINativeMethod gMethods[] = {
-  NATIVE_METHOD(FinalizerReference, makeCircularListIfUnenqueued, "!()Z"),
-  NATIVE_METHOD(FinalizerReference, getReferent, "!()Ljava/lang/Object;"),
+  FAST_NATIVE_METHOD(FinalizerReference, makeCircularListIfUnenqueued, "()Z"),
+  FAST_NATIVE_METHOD(FinalizerReference, getReferent, "()Ljava/lang/Object;"),
 };
 
 void register_java_lang_ref_FinalizerReference(JNIEnv* env) {
diff --git a/runtime/native/java_lang_ref_Reference.cc b/runtime/native/java_lang_ref_Reference.cc
index c778068..b1cb2f2 100644
--- a/runtime/native/java_lang_ref_Reference.cc
+++ b/runtime/native/java_lang_ref_Reference.cc
@@ -40,8 +40,8 @@
 }
 
 static JNINativeMethod gMethods[] = {
-  NATIVE_METHOD(Reference, getReferent, "!()Ljava/lang/Object;"),
-  NATIVE_METHOD(Reference, clearReferent, "!()V"),
+  FAST_NATIVE_METHOD(Reference, getReferent, "()Ljava/lang/Object;"),
+  FAST_NATIVE_METHOD(Reference, clearReferent, "()V"),
 };
 
 void register_java_lang_ref_Reference(JNIEnv* env) {
diff --git a/runtime/native/java_lang_reflect_Array.cc b/runtime/native/java_lang_reflect_Array.cc
index d827f81..54c2109 100644
--- a/runtime/native/java_lang_reflect_Array.cc
+++ b/runtime/native/java_lang_reflect_Array.cc
@@ -72,8 +72,8 @@
 }
 
 static JNINativeMethod gMethods[] = {
-  NATIVE_METHOD(Array, createMultiArray, "!(Ljava/lang/Class;[I)Ljava/lang/Object;"),
-  NATIVE_METHOD(Array, createObjectArray, "!(Ljava/lang/Class;I)Ljava/lang/Object;"),
+  FAST_NATIVE_METHOD(Array, createMultiArray, "(Ljava/lang/Class;[I)Ljava/lang/Object;"),
+  FAST_NATIVE_METHOD(Array, createObjectArray, "(Ljava/lang/Class;I)Ljava/lang/Object;"),
 };
 
 void register_java_lang_reflect_Array(JNIEnv* env) {
diff --git a/runtime/native/java_lang_reflect_Constructor.cc b/runtime/native/java_lang_reflect_Constructor.cc
index 66a5359..fb78046 100644
--- a/runtime/native/java_lang_reflect_Constructor.cc
+++ b/runtime/native/java_lang_reflect_Constructor.cc
@@ -124,9 +124,9 @@
 }
 
 static JNINativeMethod gMethods[] = {
-  NATIVE_METHOD(Constructor, getExceptionTypes, "!()[Ljava/lang/Class;"),
-  NATIVE_METHOD(Constructor, newInstance0, "!([Ljava/lang/Object;)Ljava/lang/Object;"),
-  NATIVE_METHOD(Constructor, newInstanceFromSerialization, "!(Ljava/lang/Class;Ljava/lang/Class;)Ljava/lang/Object;"),
+  FAST_NATIVE_METHOD(Constructor, getExceptionTypes, "()[Ljava/lang/Class;"),
+  FAST_NATIVE_METHOD(Constructor, newInstance0, "([Ljava/lang/Object;)Ljava/lang/Object;"),
+  FAST_NATIVE_METHOD(Constructor, newInstanceFromSerialization, "(Ljava/lang/Class;Ljava/lang/Class;)Ljava/lang/Object;"),
 };
 
 void register_java_lang_reflect_Constructor(JNIEnv* env) {
diff --git a/runtime/native/java_lang_reflect_Executable.cc b/runtime/native/java_lang_reflect_Executable.cc
index 2a39428..bc23bed 100644
--- a/runtime/native/java_lang_reflect_Executable.cc
+++ b/runtime/native/java_lang_reflect_Executable.cc
@@ -195,14 +195,14 @@
 }
 
 static JNINativeMethod gMethods[] = {
-  NATIVE_METHOD(Executable, getAnnotationNative,
-                "!(Ljava/lang/Class;)Ljava/lang/annotation/Annotation;"),
-  NATIVE_METHOD(Executable, getDeclaredAnnotationsNative, "!()[Ljava/lang/annotation/Annotation;"),
-  NATIVE_METHOD(Executable, getParameterAnnotationsNative,
-                "!()[[Ljava/lang/annotation/Annotation;"),
-  NATIVE_METHOD(Executable, getParameters0, "!()[Ljava/lang/reflect/Parameter;"),
-  NATIVE_METHOD(Executable, getSignatureAnnotation, "!()[Ljava/lang/String;"),
-  NATIVE_METHOD(Executable, isAnnotationPresentNative, "!(Ljava/lang/Class;)Z"),
+  FAST_NATIVE_METHOD(Executable, getAnnotationNative,
+                "(Ljava/lang/Class;)Ljava/lang/annotation/Annotation;"),
+  FAST_NATIVE_METHOD(Executable, getDeclaredAnnotationsNative, "()[Ljava/lang/annotation/Annotation;"),
+  FAST_NATIVE_METHOD(Executable, getParameterAnnotationsNative,
+                "()[[Ljava/lang/annotation/Annotation;"),
+  FAST_NATIVE_METHOD(Executable, getParameters0, "()[Ljava/lang/reflect/Parameter;"),
+  FAST_NATIVE_METHOD(Executable, getSignatureAnnotation, "()[Ljava/lang/String;"),
+  FAST_NATIVE_METHOD(Executable, isAnnotationPresentNative, "(Ljava/lang/Class;)Z"),
 };
 
 void register_java_lang_reflect_Executable(JNIEnv* env) {
diff --git a/runtime/native/java_lang_reflect_Field.cc b/runtime/native/java_lang_reflect_Field.cc
index 374eeb5..9cf80a5 100644
--- a/runtime/native/java_lang_reflect_Field.cc
+++ b/runtime/native/java_lang_reflect_Field.cc
@@ -493,30 +493,30 @@
 }
 
 static JNINativeMethod gMethods[] = {
-  NATIVE_METHOD(Field, get,        "!(Ljava/lang/Object;)Ljava/lang/Object;"),
-  NATIVE_METHOD(Field, getBoolean, "!(Ljava/lang/Object;)Z"),
-  NATIVE_METHOD(Field, getByte,    "!(Ljava/lang/Object;)B"),
-  NATIVE_METHOD(Field, getChar,    "!(Ljava/lang/Object;)C"),
-  NATIVE_METHOD(Field, getAnnotationNative,
-                "!(Ljava/lang/Class;)Ljava/lang/annotation/Annotation;"),
-  NATIVE_METHOD(Field, getArtField, "!()J"),
-  NATIVE_METHOD(Field, getDeclaredAnnotations, "!()[Ljava/lang/annotation/Annotation;"),
-  NATIVE_METHOD(Field, getSignatureAnnotation, "!()[Ljava/lang/String;"),
-  NATIVE_METHOD(Field, getDouble,  "!(Ljava/lang/Object;)D"),
-  NATIVE_METHOD(Field, getFloat,   "!(Ljava/lang/Object;)F"),
-  NATIVE_METHOD(Field, getInt,     "!(Ljava/lang/Object;)I"),
-  NATIVE_METHOD(Field, getLong,    "!(Ljava/lang/Object;)J"),
-  NATIVE_METHOD(Field, getShort,   "!(Ljava/lang/Object;)S"),
-  NATIVE_METHOD(Field, isAnnotationPresentNative, "!(Ljava/lang/Class;)Z"),
-  NATIVE_METHOD(Field, set,        "!(Ljava/lang/Object;Ljava/lang/Object;)V"),
-  NATIVE_METHOD(Field, setBoolean, "!(Ljava/lang/Object;Z)V"),
-  NATIVE_METHOD(Field, setByte,    "!(Ljava/lang/Object;B)V"),
-  NATIVE_METHOD(Field, setChar,    "!(Ljava/lang/Object;C)V"),
-  NATIVE_METHOD(Field, setDouble,  "!(Ljava/lang/Object;D)V"),
-  NATIVE_METHOD(Field, setFloat,   "!(Ljava/lang/Object;F)V"),
-  NATIVE_METHOD(Field, setInt,     "!(Ljava/lang/Object;I)V"),
-  NATIVE_METHOD(Field, setLong,    "!(Ljava/lang/Object;J)V"),
-  NATIVE_METHOD(Field, setShort,   "!(Ljava/lang/Object;S)V"),
+  FAST_NATIVE_METHOD(Field, get,        "(Ljava/lang/Object;)Ljava/lang/Object;"),
+  FAST_NATIVE_METHOD(Field, getBoolean, "(Ljava/lang/Object;)Z"),
+  FAST_NATIVE_METHOD(Field, getByte,    "(Ljava/lang/Object;)B"),
+  FAST_NATIVE_METHOD(Field, getChar,    "(Ljava/lang/Object;)C"),
+  FAST_NATIVE_METHOD(Field, getAnnotationNative,
+                "(Ljava/lang/Class;)Ljava/lang/annotation/Annotation;"),
+  FAST_NATIVE_METHOD(Field, getArtField, "()J"),
+  FAST_NATIVE_METHOD(Field, getDeclaredAnnotations, "()[Ljava/lang/annotation/Annotation;"),
+  FAST_NATIVE_METHOD(Field, getSignatureAnnotation, "()[Ljava/lang/String;"),
+  FAST_NATIVE_METHOD(Field, getDouble,  "(Ljava/lang/Object;)D"),
+  FAST_NATIVE_METHOD(Field, getFloat,   "(Ljava/lang/Object;)F"),
+  FAST_NATIVE_METHOD(Field, getInt,     "(Ljava/lang/Object;)I"),
+  FAST_NATIVE_METHOD(Field, getLong,    "(Ljava/lang/Object;)J"),
+  FAST_NATIVE_METHOD(Field, getShort,   "(Ljava/lang/Object;)S"),
+  FAST_NATIVE_METHOD(Field, isAnnotationPresentNative, "(Ljava/lang/Class;)Z"),
+  FAST_NATIVE_METHOD(Field, set,        "(Ljava/lang/Object;Ljava/lang/Object;)V"),
+  FAST_NATIVE_METHOD(Field, setBoolean, "(Ljava/lang/Object;Z)V"),
+  FAST_NATIVE_METHOD(Field, setByte,    "(Ljava/lang/Object;B)V"),
+  FAST_NATIVE_METHOD(Field, setChar,    "(Ljava/lang/Object;C)V"),
+  FAST_NATIVE_METHOD(Field, setDouble,  "(Ljava/lang/Object;D)V"),
+  FAST_NATIVE_METHOD(Field, setFloat,   "(Ljava/lang/Object;F)V"),
+  FAST_NATIVE_METHOD(Field, setInt,     "(Ljava/lang/Object;I)V"),
+  FAST_NATIVE_METHOD(Field, setLong,    "(Ljava/lang/Object;J)V"),
+  FAST_NATIVE_METHOD(Field, setShort,   "(Ljava/lang/Object;S)V"),
 };
 
 void register_java_lang_reflect_Field(JNIEnv* env) {
diff --git a/runtime/native/java_lang_reflect_Method.cc b/runtime/native/java_lang_reflect_Method.cc
index a6589bc..6e5e3d9 100644
--- a/runtime/native/java_lang_reflect_Method.cc
+++ b/runtime/native/java_lang_reflect_Method.cc
@@ -84,9 +84,9 @@
 }
 
 static JNINativeMethod gMethods[] = {
-  NATIVE_METHOD(Method, getDefaultValue, "!()Ljava/lang/Object;"),
-  NATIVE_METHOD(Method, getExceptionTypes, "!()[Ljava/lang/Class;"),
-  NATIVE_METHOD(Method, invoke, "!(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;"),
+  FAST_NATIVE_METHOD(Method, getDefaultValue, "()Ljava/lang/Object;"),
+  FAST_NATIVE_METHOD(Method, getExceptionTypes, "()[Ljava/lang/Class;"),
+  FAST_NATIVE_METHOD(Method, invoke, "(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;"),
 };
 
 void register_java_lang_reflect_Method(JNIEnv* env) {
diff --git a/runtime/native/java_lang_reflect_Parameter.cc b/runtime/native/java_lang_reflect_Parameter.cc
index 0bb9e38..37aa16c 100644
--- a/runtime/native/java_lang_reflect_Parameter.cc
+++ b/runtime/native/java_lang_reflect_Parameter.cc
@@ -63,9 +63,9 @@
 }
 
 static JNINativeMethod gMethods[] = {
-  NATIVE_METHOD(Parameter,
+  FAST_NATIVE_METHOD(Parameter,
                 getAnnotationNative,
-                "!(Ljava/lang/reflect/Executable;ILjava/lang/Class;)Ljava/lang/annotation/Annotation;"),
+                "(Ljava/lang/reflect/Executable;ILjava/lang/Class;)Ljava/lang/annotation/Annotation;"),
 };
 
 void register_java_lang_reflect_Parameter(JNIEnv* env) {
diff --git a/runtime/native/java_lang_reflect_Proxy.cc b/runtime/native/java_lang_reflect_Proxy.cc
index 70cd6aa..0279b5f 100644
--- a/runtime/native/java_lang_reflect_Proxy.cc
+++ b/runtime/native/java_lang_reflect_Proxy.cc
@@ -35,7 +35,7 @@
 }
 
 static JNINativeMethod gMethods[] = {
-  NATIVE_METHOD(Proxy, generateProxy, "!(Ljava/lang/String;[Ljava/lang/Class;Ljava/lang/ClassLoader;[Ljava/lang/reflect/Method;[[Ljava/lang/Class;)Ljava/lang/Class;"),
+  FAST_NATIVE_METHOD(Proxy, generateProxy, "(Ljava/lang/String;[Ljava/lang/Class;Ljava/lang/ClassLoader;[Ljava/lang/reflect/Method;[[Ljava/lang/Class;)Ljava/lang/Class;"),
 };
 
 void register_java_lang_reflect_Proxy(JNIEnv* env) {
diff --git a/runtime/native/libcore_util_CharsetUtils.cc b/runtime/native/libcore_util_CharsetUtils.cc
index e51b6d2..4138ccc 100644
--- a/runtime/native/libcore_util_CharsetUtils.cc
+++ b/runtime/native/libcore_util_CharsetUtils.cc
@@ -249,11 +249,11 @@
 }
 
 static JNINativeMethod gMethods[] = {
-  NATIVE_METHOD(CharsetUtils, asciiBytesToChars, "!([BII[C)V"),
-  NATIVE_METHOD(CharsetUtils, isoLatin1BytesToChars, "!([BII[C)V"),
-  NATIVE_METHOD(CharsetUtils, toAsciiBytes, "!(Ljava/lang/String;II)[B"),
-  NATIVE_METHOD(CharsetUtils, toIsoLatin1Bytes, "!(Ljava/lang/String;II)[B"),
-  NATIVE_METHOD(CharsetUtils, toUtf8Bytes, "!(Ljava/lang/String;II)[B"),
+  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"),
 };
 
 void register_libcore_util_CharsetUtils(JNIEnv* env) {
diff --git a/runtime/native/org_apache_harmony_dalvik_ddmc_DdmServer.cc b/runtime/native/org_apache_harmony_dalvik_ddmc_DdmServer.cc
index 5356498..5809708 100644
--- a/runtime/native/org_apache_harmony_dalvik_ddmc_DdmServer.cc
+++ b/runtime/native/org_apache_harmony_dalvik_ddmc_DdmServer.cc
@@ -33,7 +33,7 @@
 }
 
 static JNINativeMethod gMethods[] = {
-  NATIVE_METHOD(DdmServer, nativeSendChunk, "!(I[BII)V"),
+  FAST_NATIVE_METHOD(DdmServer, nativeSendChunk, "(I[BII)V"),
 };
 
 void register_org_apache_harmony_dalvik_ddmc_DdmServer(JNIEnv* env) {
diff --git a/runtime/native/org_apache_harmony_dalvik_ddmc_DdmVmInternal.cc b/runtime/native/org_apache_harmony_dalvik_ddmc_DdmVmInternal.cc
index ca17c26..69ef59e 100644
--- a/runtime/native/org_apache_harmony_dalvik_ddmc_DdmVmInternal.cc
+++ b/runtime/native/org_apache_harmony_dalvik_ddmc_DdmVmInternal.cc
@@ -165,11 +165,11 @@
 
 static JNINativeMethod gMethods[] = {
   NATIVE_METHOD(DdmVmInternal, enableRecentAllocations, "(Z)V"),
-  NATIVE_METHOD(DdmVmInternal, getRecentAllocations, "!()[B"),
-  NATIVE_METHOD(DdmVmInternal, getRecentAllocationStatus, "!()Z"),
+  FAST_NATIVE_METHOD(DdmVmInternal, getRecentAllocations, "()[B"),
+  FAST_NATIVE_METHOD(DdmVmInternal, getRecentAllocationStatus, "()Z"),
   NATIVE_METHOD(DdmVmInternal, getStackTraceById, "(I)[Ljava/lang/StackTraceElement;"),
   NATIVE_METHOD(DdmVmInternal, getThreadStats, "()[B"),
-  NATIVE_METHOD(DdmVmInternal, heapInfoNotify, "!(I)Z"),
+  FAST_NATIVE_METHOD(DdmVmInternal, heapInfoNotify, "(I)Z"),
   NATIVE_METHOD(DdmVmInternal, heapSegmentNotify, "(IIZ)Z"),
   NATIVE_METHOD(DdmVmInternal, threadNotify, "(Z)V"),
 };
diff --git a/runtime/native/scoped_fast_native_object_access-inl.h b/runtime/native/scoped_fast_native_object_access-inl.h
index 1d73813..50a554c 100644
--- a/runtime/native/scoped_fast_native_object_access-inl.h
+++ b/runtime/native/scoped_fast_native_object_access-inl.h
@@ -27,7 +27,7 @@
 inline ScopedFastNativeObjectAccess::ScopedFastNativeObjectAccess(JNIEnv* env)
     : ScopedObjectAccessAlreadyRunnable(env) {
   Locks::mutator_lock_->AssertSharedHeld(Self());
-  DCHECK((*Self()->GetManagedStack()->GetTopQuickFrame())->IsFastNative());
+  DCHECK((*Self()->GetManagedStack()->GetTopQuickFrame())->IsAnnotatedWithFastNative());
   // Don't work with raw objects in non-runnable states.
   DCHECK_EQ(Self()->GetState(), kRunnable);
 }
diff --git a/runtime/native/sun_misc_Unsafe.cc b/runtime/native/sun_misc_Unsafe.cc
index 644df07..cc5a41a 100644
--- a/runtime/native/sun_misc_Unsafe.cc
+++ b/runtime/native/sun_misc_Unsafe.cc
@@ -492,69 +492,69 @@
 }
 
 static JNINativeMethod gMethods[] = {
-  NATIVE_METHOD(Unsafe, compareAndSwapInt, "!(Ljava/lang/Object;JII)Z"),
-  NATIVE_METHOD(Unsafe, compareAndSwapLong, "!(Ljava/lang/Object;JJJ)Z"),
-  NATIVE_METHOD(Unsafe, compareAndSwapObject, "!(Ljava/lang/Object;JLjava/lang/Object;Ljava/lang/Object;)Z"),
-  NATIVE_METHOD(Unsafe, getIntVolatile, "!(Ljava/lang/Object;J)I"),
-  NATIVE_METHOD(Unsafe, putIntVolatile, "!(Ljava/lang/Object;JI)V"),
-  NATIVE_METHOD(Unsafe, getLongVolatile, "!(Ljava/lang/Object;J)J"),
-  NATIVE_METHOD(Unsafe, putLongVolatile, "!(Ljava/lang/Object;JJ)V"),
-  NATIVE_METHOD(Unsafe, getObjectVolatile, "!(Ljava/lang/Object;J)Ljava/lang/Object;"),
-  NATIVE_METHOD(Unsafe, putObjectVolatile, "!(Ljava/lang/Object;JLjava/lang/Object;)V"),
-  NATIVE_METHOD(Unsafe, getInt, "!(Ljava/lang/Object;J)I"),
-  NATIVE_METHOD(Unsafe, putInt, "!(Ljava/lang/Object;JI)V"),
-  NATIVE_METHOD(Unsafe, putOrderedInt, "!(Ljava/lang/Object;JI)V"),
-  NATIVE_METHOD(Unsafe, getLong, "!(Ljava/lang/Object;J)J"),
-  NATIVE_METHOD(Unsafe, putLong, "!(Ljava/lang/Object;JJ)V"),
-  NATIVE_METHOD(Unsafe, putOrderedLong, "!(Ljava/lang/Object;JJ)V"),
-  NATIVE_METHOD(Unsafe, getObject, "!(Ljava/lang/Object;J)Ljava/lang/Object;"),
-  NATIVE_METHOD(Unsafe, putObject, "!(Ljava/lang/Object;JLjava/lang/Object;)V"),
-  NATIVE_METHOD(Unsafe, putOrderedObject, "!(Ljava/lang/Object;JLjava/lang/Object;)V"),
-  NATIVE_METHOD(Unsafe, getArrayBaseOffsetForComponentType, "!(Ljava/lang/Class;)I"),
-  NATIVE_METHOD(Unsafe, getArrayIndexScaleForComponentType, "!(Ljava/lang/Class;)I"),
-  NATIVE_METHOD(Unsafe, addressSize, "!()I"),
-  NATIVE_METHOD(Unsafe, pageSize, "!()I"),
-  NATIVE_METHOD(Unsafe, allocateMemory, "!(J)J"),
-  NATIVE_METHOD(Unsafe, freeMemory, "!(J)V"),
-  NATIVE_METHOD(Unsafe, setMemory, "!(JJB)V"),
-  NATIVE_METHOD(Unsafe, copyMemory, "!(JJJ)V"),
-  NATIVE_METHOD(Unsafe, copyMemoryToPrimitiveArray, "!(JLjava/lang/Object;JJ)V"),
-  NATIVE_METHOD(Unsafe, copyMemoryFromPrimitiveArray, "!(Ljava/lang/Object;JJJ)V"),
-  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, 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, copyMemory, "(JJJ)V"),
+  FAST_NATIVE_METHOD(Unsafe, copyMemoryToPrimitiveArray, "(JLjava/lang/Object;JJ)V"),
+  FAST_NATIVE_METHOD(Unsafe, copyMemoryFromPrimitiveArray, "(Ljava/lang/Object;JJJ)V"),
+  FAST_NATIVE_METHOD(Unsafe, getBoolean, "(Ljava/lang/Object;J)Z"),
 
-  NATIVE_METHOD(Unsafe, getByte, "!(Ljava/lang/Object;J)B"),
-  NATIVE_METHOD(Unsafe, getChar, "!(Ljava/lang/Object;J)C"),
-  NATIVE_METHOD(Unsafe, getShort, "!(Ljava/lang/Object;J)S"),
-  NATIVE_METHOD(Unsafe, getFloat, "!(Ljava/lang/Object;J)F"),
-  NATIVE_METHOD(Unsafe, getDouble, "!(Ljava/lang/Object;J)D"),
-  NATIVE_METHOD(Unsafe, putBoolean, "!(Ljava/lang/Object;JZ)V"),
-  NATIVE_METHOD(Unsafe, putByte, "!(Ljava/lang/Object;JB)V"),
-  NATIVE_METHOD(Unsafe, putChar, "!(Ljava/lang/Object;JC)V"),
-  NATIVE_METHOD(Unsafe, putShort, "!(Ljava/lang/Object;JS)V"),
-  NATIVE_METHOD(Unsafe, putFloat, "!(Ljava/lang/Object;JF)V"),
-  NATIVE_METHOD(Unsafe, putDouble, "!(Ljava/lang/Object;JD)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"),
 
   // Each of the getFoo variants are overloaded with a call that operates
   // directively on a native pointer.
-  OVERLOADED_NATIVE_METHOD(Unsafe, getByte, "!(J)B", getByteJ),
-  OVERLOADED_NATIVE_METHOD(Unsafe, getChar, "!(J)C", getCharJ),
-  OVERLOADED_NATIVE_METHOD(Unsafe, getShort, "!(J)S", getShortJ),
-  OVERLOADED_NATIVE_METHOD(Unsafe, getInt, "!(J)I", getIntJ),
-  OVERLOADED_NATIVE_METHOD(Unsafe, getLong, "!(J)J", getLongJ),
-  OVERLOADED_NATIVE_METHOD(Unsafe, getFloat, "!(J)F", getFloatJ),
-  OVERLOADED_NATIVE_METHOD(Unsafe, getDouble, "!(J)D", getDoubleJ),
-  OVERLOADED_NATIVE_METHOD(Unsafe, putByte, "!(JB)V", putByteJB),
-  OVERLOADED_NATIVE_METHOD(Unsafe, putChar, "!(JC)V", putCharJC),
-  OVERLOADED_NATIVE_METHOD(Unsafe, putShort, "!(JS)V", putShortJS),
-  OVERLOADED_NATIVE_METHOD(Unsafe, putInt, "!(JI)V", putIntJI),
-  OVERLOADED_NATIVE_METHOD(Unsafe, putLong, "!(JJ)V", putLongJJ),
-  OVERLOADED_NATIVE_METHOD(Unsafe, putFloat, "!(JF)V", putFloatJF),
-  OVERLOADED_NATIVE_METHOD(Unsafe, putDouble, "!(JD)V", putDoubleJD),
+  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
-  NATIVE_METHOD(Unsafe, loadFence, "!()V"),
-  NATIVE_METHOD(Unsafe, storeFence, "!()V"),
-  NATIVE_METHOD(Unsafe, fullFence, "!()V"),
+  FAST_NATIVE_METHOD(Unsafe, loadFence, "()V"),
+  FAST_NATIVE_METHOD(Unsafe, storeFence, "()V"),
+  FAST_NATIVE_METHOD(Unsafe, fullFence, "()V"),
 };
 
 void register_sun_misc_Unsafe(JNIEnv* env) {
diff --git a/runtime/oat.h b/runtime/oat.h
index 656b868..1544121 100644
--- a/runtime/oat.h
+++ b/runtime/oat.h
@@ -32,7 +32,7 @@
 class PACKED(4) OatHeader {
  public:
   static constexpr uint8_t kOatMagic[] = { 'o', 'a', 't', '\n' };
-  static constexpr uint8_t kOatVersion[] = { '1', '1', '3', '\0' };  // Invoke info change.
+  static constexpr uint8_t kOatVersion[] = { '1', '1', '4', '\0' };  // hash-based DexCache types.
 
   static constexpr const char* kImageLocationKey = "image-location";
   static constexpr const char* kDex2OatCmdLineKey = "dex2oat-cmdline";
diff --git a/runtime/obj_ptr-inl.h b/runtime/obj_ptr-inl.h
index d0be6dc..f2921da 100644
--- a/runtime/obj_ptr-inl.h
+++ b/runtime/obj_ptr-inl.h
@@ -22,27 +22,27 @@
 
 namespace art {
 
-template<class MirrorType, bool kPoison>
-inline bool ObjPtr<MirrorType, kPoison>::IsValid() const {
-  if (!kPoison || IsNull()) {
+template<class MirrorType>
+inline bool ObjPtr<MirrorType>::IsValid() const {
+  if (!kObjPtrPoisoning || IsNull()) {
     return true;
   }
   return GetCookie() == TrimCookie(Thread::Current()->GetPoisonObjectCookie());
 }
 
-template<class MirrorType, bool kPoison>
-inline void ObjPtr<MirrorType, kPoison>::AssertValid() const {
-  if (kPoison) {
+template<class MirrorType>
+inline void ObjPtr<MirrorType>::AssertValid() const {
+  if (kObjPtrPoisoning) {
     CHECK(IsValid()) << "Stale object pointer " << PtrUnchecked() << " , expected cookie "
         << TrimCookie(Thread::Current()->GetPoisonObjectCookie()) << " but got " << GetCookie();
   }
 }
 
-template<class MirrorType, bool kPoison>
-inline uintptr_t ObjPtr<MirrorType, kPoison>::Encode(MirrorType* ptr) {
+template<class MirrorType>
+inline uintptr_t ObjPtr<MirrorType>::Encode(MirrorType* ptr) {
   uintptr_t ref = reinterpret_cast<uintptr_t>(ptr);
   DCHECK_ALIGNED(ref, kObjectAlignment);
-  if (kPoison && ref != 0) {
+  if (kObjPtrPoisoning && ref != 0) {
     DCHECK_LE(ref, 0xFFFFFFFFU);
     ref >>= kObjectAlignmentShift;
     // Put cookie in high bits.
@@ -53,8 +53,8 @@
   return ref;
 }
 
-template<class MirrorType, bool kPoison>
-inline std::ostream& operator<<(std::ostream& os, ObjPtr<MirrorType, kPoison> ptr) {
+template<class MirrorType>
+inline std::ostream& operator<<(std::ostream& os, ObjPtr<MirrorType> ptr) {
   // May be used for dumping bad pointers, do not use the checked version.
   return os << ptr.PtrUnchecked();
 }
diff --git a/runtime/obj_ptr.h b/runtime/obj_ptr.h
index 2da2ae5..92cf4eb 100644
--- a/runtime/obj_ptr.h
+++ b/runtime/obj_ptr.h
@@ -26,10 +26,12 @@
 
 namespace art {
 
+constexpr bool kObjPtrPoisoning = kIsDebugBuild;
+
 // Value type representing a pointer to a mirror::Object of type MirrorType
 // Pass kPoison as a template boolean for testing in non-debug builds.
 // Since the cookie is thread based, it is not safe to share an ObjPtr between threads.
-template<class MirrorType, bool kPoison = kIsDebugBuild>
+template<class MirrorType>
 class ObjPtr {
   static constexpr size_t kCookieShift =
       sizeof(kHeapReferenceSize) * kBitsPerByte - kObjectAlignmentShift;
@@ -60,14 +62,14 @@
 
   template <typename Type,
             typename = typename std::enable_if<std::is_base_of<MirrorType, Type>::value>::type>
-  ALWAYS_INLINE ObjPtr(const ObjPtr<Type, kPoison>& other)  // NOLINT
+  ALWAYS_INLINE ObjPtr(const ObjPtr<Type>& other)  // NOLINT
       REQUIRES_SHARED(Locks::mutator_lock_)
       : reference_(Encode(static_cast<MirrorType*>(other.Ptr()))) {
   }
 
   template <typename Type,
             typename = typename std::enable_if<std::is_base_of<MirrorType, Type>::value>::type>
-  ALWAYS_INLINE ObjPtr& operator=(const ObjPtr<Type, kPoison>& other)
+  ALWAYS_INLINE ObjPtr& operator=(const ObjPtr<Type>& other)
       REQUIRES_SHARED(Locks::mutator_lock_) {
     reference_ = Encode(static_cast<MirrorType*>(other.Ptr()));
     return *this;
@@ -130,7 +132,7 @@
 
   // Ptr unchecked does not check that object pointer is valid. Do not use if you can avoid it.
   ALWAYS_INLINE MirrorType* PtrUnchecked() const {
-    if (kPoison) {
+    if (kObjPtrPoisoning) {
       return reinterpret_cast<MirrorType*>(
           static_cast<uintptr_t>(static_cast<uint32_t>(reference_ << kObjectAlignmentShift)));
     } else {
@@ -167,46 +169,46 @@
 // Hash function for stl data structures.
 class HashObjPtr {
  public:
-  template<class MirrorType, bool kPoison>
-  size_t operator()(const ObjPtr<MirrorType, kPoison>& ptr) const NO_THREAD_SAFETY_ANALYSIS {
+  template<class MirrorType>
+  size_t operator()(const ObjPtr<MirrorType>& ptr) const NO_THREAD_SAFETY_ANALYSIS {
     return std::hash<MirrorType*>()(ptr.Ptr());
   }
 };
 
-template<class MirrorType, bool kPoison, typename PointerType>
-ALWAYS_INLINE bool operator==(const PointerType* a, const ObjPtr<MirrorType, kPoison>& b)
+template<class MirrorType, typename PointerType>
+ALWAYS_INLINE bool operator==(const PointerType* a, const ObjPtr<MirrorType>& b)
     REQUIRES_SHARED(Locks::mutator_lock_) {
   return b == a;
 }
 
-template<class MirrorType, bool kPoison>
-ALWAYS_INLINE bool operator==(std::nullptr_t, const ObjPtr<MirrorType, kPoison>& b) {
+template<class MirrorType>
+ALWAYS_INLINE bool operator==(std::nullptr_t, const ObjPtr<MirrorType>& b) {
   return b == nullptr;
 }
 
-template<typename MirrorType, bool kPoison, typename PointerType>
-ALWAYS_INLINE bool operator!=(const PointerType* a, const ObjPtr<MirrorType, kPoison>& b)
+template<typename MirrorType, typename PointerType>
+ALWAYS_INLINE bool operator!=(const PointerType* a, const ObjPtr<MirrorType>& b)
     REQUIRES_SHARED(Locks::mutator_lock_) {
   return b != a;
 }
 
-template<class MirrorType, bool kPoison>
-ALWAYS_INLINE bool operator!=(std::nullptr_t, const ObjPtr<MirrorType, kPoison>& b) {
+template<class MirrorType>
+ALWAYS_INLINE bool operator!=(std::nullptr_t, const ObjPtr<MirrorType>& b) {
   return b != nullptr;
 }
 
-template<class MirrorType, bool kPoison = kIsDebugBuild>
-static inline ObjPtr<MirrorType, kPoison> MakeObjPtr(MirrorType* ptr) {
-  return ObjPtr<MirrorType, kPoison>(ptr);
+template<class MirrorType>
+static inline ObjPtr<MirrorType> MakeObjPtr(MirrorType* ptr) {
+  return ObjPtr<MirrorType>(ptr);
 }
 
-template<class MirrorType, bool kPoison = kIsDebugBuild>
-static inline ObjPtr<MirrorType, kPoison> MakeObjPtr(ObjPtr<MirrorType, kPoison> ptr) {
-  return ObjPtr<MirrorType, kPoison>(ptr);
+template<class MirrorType>
+static inline ObjPtr<MirrorType> MakeObjPtr(ObjPtr<MirrorType> ptr) {
+  return ObjPtr<MirrorType>(ptr);
 }
 
-template<class MirrorType, bool kPoison>
-ALWAYS_INLINE std::ostream& operator<<(std::ostream& os, ObjPtr<MirrorType, kPoison> ptr);
+template<class MirrorType>
+ALWAYS_INLINE std::ostream& operator<<(std::ostream& os, ObjPtr<MirrorType> ptr);
 
 }  // namespace art
 
diff --git a/runtime/object_callbacks.h b/runtime/object_callbacks.h
index 4d726ec..ea5e698 100644
--- a/runtime/object_callbacks.h
+++ b/runtime/object_callbacks.h
@@ -43,7 +43,8 @@
   // May return the same address as the input if the object did not move.
   virtual mirror::Object* MarkObject(mirror::Object* obj) = 0;
   // Mark an object and update the value stored in the heap reference if the object moved.
-  virtual void MarkHeapReference(mirror::HeapReference<mirror::Object>* obj) = 0;
+  virtual void MarkHeapReference(mirror::HeapReference<mirror::Object>* obj,
+                                 bool do_atomic_update) = 0;
 };
 
 }  // namespace art
diff --git a/runtime/openjdkjvmti/OpenjdkJvmTi.cc b/runtime/openjdkjvmti/OpenjdkJvmTi.cc
index a815a60..77ca9ce 100644
--- a/runtime/openjdkjvmti/OpenjdkJvmTi.cc
+++ b/runtime/openjdkjvmti/OpenjdkJvmTi.cc
@@ -1188,13 +1188,13 @@
     ENSURE_NON_NULL(name_ptr);
     switch (error) {
 #define ERROR_CASE(e) case (JVMTI_ERROR_ ## e) : do { \
-          jvmtiError res = CopyString(env, \
-                                      "JVMTI_ERROR_"#e, \
-                                      reinterpret_cast<unsigned char**>(name_ptr)); \
-          if (res != OK) { \
+          jvmtiError res; \
+          JvmtiUniquePtr<char[]> copy = CopyString(env, "JVMTI_ERROR_"#e, &res); \
+          if (copy == nullptr) { \
             *name_ptr = nullptr; \
             return res; \
           } else { \
+            *name_ptr = copy.release(); \
             return OK; \
           } \
         } while (false)
@@ -1248,13 +1248,13 @@
       ERROR_CASE(INVALID_ENVIRONMENT);
 #undef ERROR_CASE
       default: {
-        jvmtiError res = CopyString(env,
-                                    "JVMTI_ERROR_UNKNOWN",
-                                    reinterpret_cast<unsigned char**>(name_ptr));
-        if (res != OK) {
+        jvmtiError res;
+        JvmtiUniquePtr<char[]> copy = CopyString(env, "JVMTI_ERROR_UNKNOWN", &res);
+        if (copy == nullptr) {
           *name_ptr = nullptr;
           return res;
         } else {
+          *name_ptr = copy.release();
           return ERR(ILLEGAL_ARGUMENT);
         }
       }
diff --git a/runtime/openjdkjvmti/art_jvmti.h b/runtime/openjdkjvmti/art_jvmti.h
index 106165c..99139a1 100644
--- a/runtime/openjdkjvmti/art_jvmti.h
+++ b/runtime/openjdkjvmti/art_jvmti.h
@@ -33,6 +33,7 @@
 #define ART_RUNTIME_OPENJDKJVMTI_ART_JVMTI_H_
 
 #include <memory>
+#include <type_traits>
 
 #include <jni.h>
 
@@ -86,6 +87,7 @@
   return ret_value;
 }
 
+template <typename T>
 class JvmtiDeleter {
  public:
   JvmtiDeleter() : env_(nullptr) {}
@@ -95,9 +97,9 @@
   JvmtiDeleter(JvmtiDeleter&&) = default;
   JvmtiDeleter& operator=(const JvmtiDeleter&) = default;
 
-  void operator()(unsigned char* ptr) const {
+  void operator()(T* ptr) const {
     CHECK(env_ != nullptr);
-    jvmtiError ret = env_->Deallocate(ptr);
+    jvmtiError ret = env_->Deallocate(reinterpret_cast<unsigned char*>(ptr));
     CHECK(ret == ERR(NONE));
   }
 
@@ -105,12 +107,65 @@
   mutable jvmtiEnv* env_;
 };
 
-using JvmtiUniquePtr = std::unique_ptr<unsigned char, JvmtiDeleter>;
+template <typename T>
+class JvmtiDeleter<T[]> {
+  public:
+  JvmtiDeleter() : env_(nullptr) {}
+  explicit JvmtiDeleter(jvmtiEnv* env) : env_(env) {}
+
+  JvmtiDeleter(JvmtiDeleter&) = default;
+  JvmtiDeleter(JvmtiDeleter&&) = default;
+  JvmtiDeleter& operator=(const JvmtiDeleter&) = default;
+
+  template <typename U>
+  void operator()(U* ptr) const {
+    CHECK(env_ != nullptr);
+    jvmtiError ret = env_->Deallocate(reinterpret_cast<unsigned char*>(ptr));
+    CHECK(ret == ERR(NONE));
+  }
+
+ private:
+  mutable jvmtiEnv* env_;
+};
+
+template <typename T>
+using JvmtiUniquePtr = std::unique_ptr<T, JvmtiDeleter<T>>;
 
 template <typename T>
 ALWAYS_INLINE
-static inline JvmtiUniquePtr MakeJvmtiUniquePtr(jvmtiEnv* env, T* mem) {
-  return JvmtiUniquePtr(reinterpret_cast<unsigned char*>(mem), JvmtiDeleter(env));
+static inline JvmtiUniquePtr<T> MakeJvmtiUniquePtr(jvmtiEnv* env, T* mem) {
+  return JvmtiUniquePtr<T>(mem, JvmtiDeleter<T>(env));
+}
+
+template <typename T>
+ALWAYS_INLINE
+static inline JvmtiUniquePtr<T> MakeJvmtiUniquePtr(jvmtiEnv* env, unsigned char* mem) {
+  return JvmtiUniquePtr<T>(reinterpret_cast<T*>(mem), JvmtiDeleter<T>(env));
+}
+
+template <typename T>
+ALWAYS_INLINE
+static inline JvmtiUniquePtr<T> AllocJvmtiUniquePtr(jvmtiEnv* env, jvmtiError* error) {
+  unsigned char* tmp;
+  *error = env->Allocate(sizeof(T), &tmp);
+  if (*error != ERR(NONE)) {
+    return JvmtiUniquePtr<T>();
+  }
+  return JvmtiUniquePtr<T>(tmp, JvmtiDeleter<T>(env));
+}
+
+template <typename T>
+ALWAYS_INLINE
+static inline JvmtiUniquePtr<T> AllocJvmtiUniquePtr(jvmtiEnv* env,
+                                                    size_t count,
+                                                    jvmtiError* error) {
+  unsigned char* tmp;
+  *error = env->Allocate(sizeof(typename std::remove_extent<T>::type) * count, &tmp);
+  if (*error != ERR(NONE)) {
+    return JvmtiUniquePtr<T>();
+  }
+  return JvmtiUniquePtr<T>(reinterpret_cast<typename std::remove_extent<T>::type*>(tmp),
+                           JvmtiDeleter<T>(env));
 }
 
 ALWAYS_INLINE
@@ -129,15 +184,12 @@
 }
 
 ALWAYS_INLINE
-static inline jvmtiError CopyString(jvmtiEnv* env, const char* src, unsigned char** copy) {
+static inline JvmtiUniquePtr<char[]> CopyString(jvmtiEnv* env, const char* src, jvmtiError* error) {
   size_t len = strlen(src) + 1;
-  unsigned char* buf;
-  jvmtiError ret = env->Allocate(len, &buf);
-  if (ret != ERR(NONE)) {
-    return ret;
+  JvmtiUniquePtr<char[]> ret = AllocJvmtiUniquePtr<char[]>(env, len, error);
+  if (ret != nullptr) {
+    strcpy(ret.get(), src);
   }
-  strcpy(reinterpret_cast<char*>(buf), src);
-  *copy = buf;
   return ret;
 }
 
diff --git a/runtime/openjdkjvmti/ti_class.cc b/runtime/openjdkjvmti/ti_class.cc
index a8a0ded..4282e38 100644
--- a/runtime/openjdkjvmti/ti_class.cc
+++ b/runtime/openjdkjvmti/ti_class.cc
@@ -673,18 +673,17 @@
     return ERR(INVALID_CLASS);
   }
 
-  JvmtiUniquePtr sig_copy;
+  JvmtiUniquePtr<char[]> sig_copy;
   if (signature_ptr != nullptr) {
     std::string storage;
     const char* descriptor = klass->GetDescriptor(&storage);
 
-    unsigned char* tmp;
-    jvmtiError ret = CopyString(env, descriptor, &tmp);
-    if (ret != ERR(NONE)) {
+    jvmtiError ret;
+    sig_copy = CopyString(env, descriptor, &ret);
+    if (sig_copy == nullptr) {
       return ret;
     }
-    sig_copy = MakeJvmtiUniquePtr(env, tmp);
-    *signature_ptr = reinterpret_cast<char*>(tmp);
+    *signature_ptr = sig_copy.get();
   }
 
   if (generic_ptr != nullptr) {
@@ -700,12 +699,12 @@
           oss << str_array->Get(i)->ToModifiedUtf8();
         }
         std::string output_string = oss.str();
-        unsigned char* tmp;
-        jvmtiError ret = CopyString(env, output_string.c_str(), &tmp);
-        if (ret != ERR(NONE)) {
+        jvmtiError ret;
+        JvmtiUniquePtr<char[]> copy = CopyString(env, output_string.c_str(), &ret);
+        if (copy == nullptr) {
           return ret;
         }
-        *generic_ptr = reinterpret_cast<char*>(tmp);
+        *generic_ptr = copy.release();
       } else if (soa.Self()->IsExceptionPending()) {
         // TODO: Should we report an error here?
         soa.Self()->ClearException();
diff --git a/runtime/openjdkjvmti/ti_class_definition.h b/runtime/openjdkjvmti/ti_class_definition.h
index dbe5da2..3c251d4 100644
--- a/runtime/openjdkjvmti/ti_class_definition.h
+++ b/runtime/openjdkjvmti/ti_class_definition.h
@@ -46,7 +46,7 @@
   std::string name;
   jobject protection_domain;
   jint dex_len;
-  JvmtiUniquePtr dex_data;
+  JvmtiUniquePtr<unsigned char> dex_data;
   art::ArraySlice<const unsigned char> original_dex_file;
 
   ArtClassDefinition() = default;
diff --git a/runtime/openjdkjvmti/ti_class_loader.cc b/runtime/openjdkjvmti/ti_class_loader.cc
index d05f579..66357eb 100644
--- a/runtime/openjdkjvmti/ti_class_loader.cc
+++ b/runtime/openjdkjvmti/ti_class_loader.cc
@@ -105,7 +105,6 @@
   // mCookie is nulled out if the DexFile has been closed but mInternalCookie sticks around until
   // the object is finalized. Since they always point to the same array if mCookie is not null we
   // just use the mInternalCookie field. We will update one or both of these fields later.
-  // TODO Should I get the class from the classloader or directly?
   art::ArtField* internal_cookie_field = java_dex_file_obj->GetClass()->FindDeclaredInstanceField(
       "mInternalCookie", "Ljava/lang/Object;");
   // TODO Add check that mCookie is either null or same as mInternalCookie
@@ -113,7 +112,6 @@
   return internal_cookie_field->GetObject(java_dex_file_obj.Get())->AsLongArray();
 }
 
-// TODO Really wishing I had that mirror of java.lang.DexFile now.
 art::ObjPtr<art::mirror::LongArray> ClassLoaderHelper::AllocateNewDexFileCookie(
     art::Thread* self,
     art::Handle<art::mirror::LongArray> cookie,
@@ -128,8 +126,6 @@
     return nullptr;
   }
   // Copy the oat-dex field at the start.
-  // TODO Should I clear this field?
-  // TODO This is a really crappy thing here with the first element being different.
   new_cookie->SetWithoutChecks<false>(0, cookie->GetWithoutChecks(0));
   // This must match the casts in runtime/native/dalvik_system_DexFile.cc:ConvertDexFilesToJavaArray
   new_cookie->SetWithoutChecks<false>(
diff --git a/runtime/openjdkjvmti/ti_field.cc b/runtime/openjdkjvmti/ti_field.cc
index 131e6c3..8c3f2ff 100644
--- a/runtime/openjdkjvmti/ti_field.cc
+++ b/runtime/openjdkjvmti/ti_field.cc
@@ -63,31 +63,29 @@
   art::ScopedObjectAccess soa(art::Thread::Current());
   art::ArtField* art_field = art::jni::DecodeArtField(field);
 
-  JvmtiUniquePtr name_copy;
+  JvmtiUniquePtr<char[]> name_copy;
   if (name_ptr != nullptr) {
     const char* field_name = art_field->GetName();
     if (field_name == nullptr) {
       field_name = "<error>";
     }
-    unsigned char* tmp;
-    jvmtiError ret = CopyString(env, field_name, &tmp);
-    if (ret != ERR(NONE)) {
+    jvmtiError ret;
+    name_copy = CopyString(env, field_name, &ret);
+    if (name_copy == nullptr) {
       return ret;
     }
-    name_copy = MakeJvmtiUniquePtr(env, tmp);
-    *name_ptr = reinterpret_cast<char*>(tmp);
+    *name_ptr = name_copy.get();
   }
 
-  JvmtiUniquePtr signature_copy;
+  JvmtiUniquePtr<char[]> signature_copy;
   if (signature_ptr != nullptr) {
     const char* sig = art_field->GetTypeDescriptor();
-    unsigned char* tmp;
-    jvmtiError ret = CopyString(env, sig, &tmp);
-    if (ret != ERR(NONE)) {
+    jvmtiError ret;
+    signature_copy = CopyString(env, sig, &ret);
+    if (signature_copy == nullptr) {
       return ret;
     }
-    signature_copy = MakeJvmtiUniquePtr(env, tmp);
-    *signature_ptr = reinterpret_cast<char*>(tmp);
+    *signature_ptr = signature_copy.get();
   }
 
   // TODO: Support generic signature.
@@ -102,12 +100,12 @@
           oss << str_array->Get(i)->ToModifiedUtf8();
         }
         std::string output_string = oss.str();
-        unsigned char* tmp;
-        jvmtiError ret = CopyString(env, output_string.c_str(), &tmp);
-        if (ret != ERR(NONE)) {
+        jvmtiError ret;
+        JvmtiUniquePtr<char[]> copy = CopyString(env, output_string.c_str(), &ret);
+        if (copy == nullptr) {
           return ret;
         }
-        *generic_ptr = reinterpret_cast<char*>(tmp);
+        *generic_ptr = copy.release();
       } else if (soa.Self()->IsExceptionPending()) {
         // TODO: Should we report an error here?
         soa.Self()->ClearException();
diff --git a/runtime/openjdkjvmti/ti_heap.cc b/runtime/openjdkjvmti/ti_heap.cc
index fe3e52b..2fbc12b 100644
--- a/runtime/openjdkjvmti/ti_heap.cc
+++ b/runtime/openjdkjvmti/ti_heap.cc
@@ -31,6 +31,7 @@
 #include "object_callbacks.h"
 #include "object_tagging.h"
 #include "obj_ptr-inl.h"
+#include "primitive.h"
 #include "runtime.h"
 #include "scoped_thread_state_change-inl.h"
 #include "thread-inl.h"
@@ -38,14 +39,138 @@
 
 namespace openjdkjvmti {
 
+namespace {
+
+// Report the contents of a string, if a callback is set.
+jint ReportString(art::ObjPtr<art::mirror::Object> obj,
+                  jvmtiEnv* env,
+                  ObjectTagTable* tag_table,
+                  const jvmtiHeapCallbacks* cb,
+                  const void* user_data) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+  if (UNLIKELY(cb->string_primitive_value_callback != nullptr) && obj->IsString()) {
+    art::ObjPtr<art::mirror::String> str = obj->AsString();
+    int32_t string_length = str->GetLength();
+    jvmtiError alloc_error;
+    JvmtiUniquePtr<uint16_t[]> data = AllocJvmtiUniquePtr<uint16_t[]>(env,
+                                                                      string_length,
+                                                                      &alloc_error);
+    if (data == nullptr) {
+      // TODO: Not really sure what to do here. Should we abort the iteration and go all the way
+      //       back? For now just warn.
+      LOG(WARNING) << "Unable to allocate buffer for string reporting! Silently dropping value.";
+      return 0;
+    }
+
+    if (str->IsCompressed()) {
+      uint8_t* compressed_data = str->GetValueCompressed();
+      for (int32_t i = 0; i != string_length; ++i) {
+        data[i] = compressed_data[i];
+      }
+    } else {
+      // Can copy directly.
+      memcpy(data.get(), str->GetValue(), string_length * sizeof(uint16_t));
+    }
+
+    const jlong class_tag = tag_table->GetTagOrZero(obj->GetClass());
+    jlong string_tag = tag_table->GetTagOrZero(obj.Ptr());
+    const jlong saved_string_tag = string_tag;
+
+    jint result = cb->string_primitive_value_callback(class_tag,
+                                                      obj->SizeOf(),
+                                                      &string_tag,
+                                                      data.get(),
+                                                      string_length,
+                                                      const_cast<void*>(user_data));
+    if (string_tag != saved_string_tag) {
+      tag_table->Set(obj.Ptr(), string_tag);
+    }
+
+    return result;
+  }
+  return 0;
+}
+
+// Report the contents of a primitive array, if a callback is set.
+jint ReportPrimitiveArray(art::ObjPtr<art::mirror::Object> obj,
+                          jvmtiEnv* env,
+                          ObjectTagTable* tag_table,
+                          const jvmtiHeapCallbacks* cb,
+                          const void* user_data) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+  if (UNLIKELY(cb->array_primitive_value_callback != nullptr) &&
+      obj->IsArrayInstance() &&
+      !obj->IsObjectArray()) {
+    art::ObjPtr<art::mirror::Array> array = obj->AsArray();
+    int32_t array_length = array->GetLength();
+    size_t component_size = array->GetClass()->GetComponentSize();
+    art::Primitive::Type art_prim_type = array->GetClass()->GetComponentType()->GetPrimitiveType();
+    jvmtiPrimitiveType prim_type =
+        static_cast<jvmtiPrimitiveType>(art::Primitive::Descriptor(art_prim_type)[0]);
+    DCHECK(prim_type == JVMTI_PRIMITIVE_TYPE_BOOLEAN ||
+           prim_type == JVMTI_PRIMITIVE_TYPE_BYTE ||
+           prim_type == JVMTI_PRIMITIVE_TYPE_CHAR ||
+           prim_type == JVMTI_PRIMITIVE_TYPE_SHORT ||
+           prim_type == JVMTI_PRIMITIVE_TYPE_INT ||
+           prim_type == JVMTI_PRIMITIVE_TYPE_LONG ||
+           prim_type == JVMTI_PRIMITIVE_TYPE_FLOAT ||
+           prim_type == JVMTI_PRIMITIVE_TYPE_DOUBLE);
+
+    const jlong class_tag = tag_table->GetTagOrZero(obj->GetClass());
+    jlong array_tag = tag_table->GetTagOrZero(obj.Ptr());
+    const jlong saved_array_tag = array_tag;
+
+    jint result;
+    if (array_length == 0) {
+      result = cb->array_primitive_value_callback(class_tag,
+                                                  obj->SizeOf(),
+                                                  &array_tag,
+                                                  0,
+                                                  prim_type,
+                                                  nullptr,
+                                                  const_cast<void*>(user_data));
+    } else {
+      jvmtiError alloc_error;
+      JvmtiUniquePtr<char[]> data = AllocJvmtiUniquePtr<char[]>(env,
+                                                                array_length * component_size,
+                                                                &alloc_error);
+      if (data == nullptr) {
+        // TODO: Not really sure what to do here. Should we abort the iteration and go all the way
+        //       back? For now just warn.
+        LOG(WARNING) << "Unable to allocate buffer for array reporting! Silently dropping value.";
+        return 0;
+      }
+
+      memcpy(data.get(), array->GetRawData(component_size, 0), array_length * component_size);
+
+      result = cb->array_primitive_value_callback(class_tag,
+                                                  obj->SizeOf(),
+                                                  &array_tag,
+                                                  array_length,
+                                                  prim_type,
+                                                  data.get(),
+                                                  const_cast<void*>(user_data));
+    }
+
+    if (array_tag != saved_array_tag) {
+      tag_table->Set(obj.Ptr(), array_tag);
+    }
+
+    return result;
+  }
+  return 0;
+}
+
+}  // namespace
+
 struct IterateThroughHeapData {
   IterateThroughHeapData(HeapUtil* _heap_util,
+                         jvmtiEnv* _env,
                          jint heap_filter,
                          art::ObjPtr<art::mirror::Class> klass,
                          const jvmtiHeapCallbacks* _callbacks,
                          const void* _user_data)
       : heap_util(_heap_util),
         filter_klass(klass),
+        env(_env),
         callbacks(_callbacks),
         user_data(_user_data),
         filter_out_tagged((heap_filter & JVMTI_HEAP_FILTER_TAGGED) != 0),
@@ -78,6 +203,7 @@
 
   HeapUtil* heap_util;
   art::ObjPtr<art::mirror::Class> filter_klass;
+  jvmtiEnv* env;
   const jvmtiHeapCallbacks* callbacks;
   const void* user_data;
   const bool filter_out_tagged;
@@ -111,8 +237,6 @@
     return;
   }
 
-  // TODO: Handle array_primitive_value_callback.
-
   if (ithd->filter_klass != nullptr) {
     if (ithd->filter_klass != klass) {
       return;
@@ -139,11 +263,28 @@
 
   ithd->stop_reports = (ret & JVMTI_VISIT_ABORT) != 0;
 
-  // TODO Implement array primitive and string primitive callback.
+  if (!ithd->stop_reports) {
+    jint string_ret = ReportString(obj,
+                                   ithd->env,
+                                   ithd->heap_util->GetTags(),
+                                   ithd->callbacks,
+                                   ithd->user_data);
+    ithd->stop_reports = (string_ret & JVMTI_VISIT_ABORT) != 0;
+  }
+
+  if (!ithd->stop_reports) {
+    jint array_ret = ReportPrimitiveArray(obj,
+                                          ithd->env,
+                                          ithd->heap_util->GetTags(),
+                                          ithd->callbacks,
+                                          ithd->user_data);
+    ithd->stop_reports = (array_ret & JVMTI_VISIT_ABORT) != 0;
+  }
+
   // TODO Implement primitive field callback.
 }
 
-jvmtiError HeapUtil::IterateThroughHeap(jvmtiEnv* env ATTRIBUTE_UNUSED,
+jvmtiError HeapUtil::IterateThroughHeap(jvmtiEnv* env,
                                         jint heap_filter,
                                         jclass klass,
                                         const jvmtiHeapCallbacks* callbacks,
@@ -152,15 +293,11 @@
     return ERR(NULL_POINTER);
   }
 
-  if (callbacks->array_primitive_value_callback != nullptr) {
-    // TODO: Implement.
-    return ERR(NOT_IMPLEMENTED);
-  }
-
   art::Thread* self = art::Thread::Current();
   art::ScopedObjectAccess soa(self);      // Now we know we have the shared lock.
 
   IterateThroughHeapData ithd(this,
+                              env,
                               heap_filter,
                               soa.Decode<art::mirror::Class>(klass),
                               callbacks,
@@ -174,10 +311,12 @@
 class FollowReferencesHelper FINAL {
  public:
   FollowReferencesHelper(HeapUtil* h,
+                         jvmtiEnv* jvmti_env,
                          art::ObjPtr<art::mirror::Object> initial_object,
                          const jvmtiHeapCallbacks* callbacks,
                          const void* user_data)
-      : tag_table_(h->GetTags()),
+      : env(jvmti_env),
+        tag_table_(h->GetTags()),
         initial_object_(initial_object),
         callbacks_(callbacks),
         user_data_(user_data),
@@ -467,6 +606,11 @@
     obj->VisitReferences<false>(visitor, art::VoidFunctor());
 
     stop_reports_ = visitor.stop_reports;
+
+    if (!stop_reports_) {
+      jint string_ret = ReportString(obj, env, tag_table_, callbacks_, user_data_);
+      stop_reports_ = (string_ret & JVMTI_VISIT_ABORT) != 0;
+    }
   }
 
   void VisitArray(art::mirror::Object* array)
@@ -498,6 +642,11 @@
           }
         }
       }
+    } else {
+      if (!stop_reports_) {
+        jint array_ret = ReportPrimitiveArray(array, env, tag_table_, callbacks_, user_data_);
+        stop_reports_ = (array_ret & JVMTI_VISIT_ABORT) != 0;
+      }
     }
   }
 
@@ -655,6 +804,7 @@
     return result;
   }
 
+  jvmtiEnv* env;
   ObjectTagTable* tag_table_;
   art::ObjPtr<art::mirror::Object> initial_object_;
   const jvmtiHeapCallbacks* callbacks_;
@@ -671,7 +821,7 @@
   friend class CollectAndReportRootsVisitor;
 };
 
-jvmtiError HeapUtil::FollowReferences(jvmtiEnv* env ATTRIBUTE_UNUSED,
+jvmtiError HeapUtil::FollowReferences(jvmtiEnv* env,
                                       jint heap_filter ATTRIBUTE_UNUSED,
                                       jclass klass ATTRIBUTE_UNUSED,
                                       jobject initial_object,
@@ -681,11 +831,6 @@
     return ERR(NULL_POINTER);
   }
 
-  if (callbacks->array_primitive_value_callback != nullptr) {
-    // TODO: Implement.
-    return ERR(NOT_IMPLEMENTED);
-  }
-
   art::Thread* self = art::Thread::Current();
 
   art::gc::Heap* heap = art::Runtime::Current()->GetHeap();
@@ -700,6 +845,7 @@
     art::ScopedSuspendAll ssa("FollowReferences");
 
     FollowReferencesHelper frh(this,
+                               env,
                                self->DecodeJObject(initial_object),
                                callbacks,
                                user_data);
diff --git a/runtime/openjdkjvmti/ti_method.cc b/runtime/openjdkjvmti/ti_method.cc
index a6cfcc1..bc73029 100644
--- a/runtime/openjdkjvmti/ti_method.cc
+++ b/runtime/openjdkjvmti/ti_method.cc
@@ -110,35 +110,32 @@
   art::ArtMethod* art_method = art::jni::DecodeArtMethod(method);
   art_method = art_method->GetInterfaceMethodIfProxy(art::kRuntimePointerSize);
 
-  JvmtiUniquePtr name_copy;
+  JvmtiUniquePtr<char[]> name_copy;
   if (name_ptr != nullptr) {
     const char* method_name = art_method->GetName();
     if (method_name == nullptr) {
       method_name = "<error>";
     }
-    unsigned char* tmp;
-    jvmtiError ret = CopyString(env, method_name, &tmp);
-    if (ret != ERR(NONE)) {
+    jvmtiError ret;
+    name_copy = CopyString(env, method_name, &ret);
+    if (name_copy == nullptr) {
       return ret;
     }
-    name_copy = MakeJvmtiUniquePtr(env, tmp);
-    *name_ptr = reinterpret_cast<char*>(tmp);
+    *name_ptr = name_copy.get();
   }
 
-  JvmtiUniquePtr signature_copy;
+  JvmtiUniquePtr<char[]> signature_copy;
   if (signature_ptr != nullptr) {
     const art::Signature sig = art_method->GetSignature();
     std::string str = sig.ToString();
-    unsigned char* tmp;
-    jvmtiError ret = CopyString(env, str.c_str(), &tmp);
-    if (ret != ERR(NONE)) {
+    jvmtiError ret;
+    signature_copy = CopyString(env, str.c_str(), &ret);
+    if (signature_copy == nullptr) {
       return ret;
     }
-    signature_copy = MakeJvmtiUniquePtr(env, tmp);
-    *signature_ptr = reinterpret_cast<char*>(tmp);
+    *signature_ptr = signature_copy.get();
   }
 
-  // TODO: Support generic signature.
   if (generic_ptr != nullptr) {
     *generic_ptr = nullptr;
     if (!art_method->GetDeclaringClass()->IsProxyClass()) {
@@ -150,12 +147,12 @@
           oss << str_array->Get(i)->ToModifiedUtf8();
         }
         std::string output_string = oss.str();
-        unsigned char* tmp;
-        jvmtiError ret = CopyString(env, output_string.c_str(), &tmp);
-        if (ret != ERR(NONE)) {
+        jvmtiError ret;
+        JvmtiUniquePtr<char[]> generic_copy = CopyString(env, output_string.c_str(), &ret);
+        if (generic_copy == nullptr) {
           return ret;
         }
-        *generic_ptr = reinterpret_cast<char*>(tmp);
+        *generic_ptr = generic_copy.release();
       } else if (soa.Self()->IsExceptionPending()) {
         // TODO: Should we report an error here?
         soa.Self()->ClearException();
diff --git a/runtime/openjdkjvmti/ti_properties.cc b/runtime/openjdkjvmti/ti_properties.cc
index 46b9e71..4f4f013 100644
--- a/runtime/openjdkjvmti/ti_properties.cc
+++ b/runtime/openjdkjvmti/ti_properties.cc
@@ -82,71 +82,69 @@
 static constexpr const char* kPropertyLibraryPath = "java.library.path";
 static constexpr const char* kPropertyClassPath = "java.class.path";
 
-static jvmtiError Copy(jvmtiEnv* env, const char* in, char** out) {
-  unsigned char* data = nullptr;
-  jvmtiError result = CopyString(env, in, &data);
-  *out = reinterpret_cast<char*>(data);
-  return result;
-}
-
 jvmtiError PropertiesUtil::GetSystemProperties(jvmtiEnv* env,
                                                jint* count_ptr,
                                                char*** property_ptr) {
   if (count_ptr == nullptr || property_ptr == nullptr) {
     return ERR(NULL_POINTER);
   }
-  unsigned char* array_data;
-  jvmtiError array_alloc_result = env->Allocate((kPropertiesSize + 2) * sizeof(char*), &array_data);
-  if (array_alloc_result != ERR(NONE)) {
+  jvmtiError array_alloc_result;
+  JvmtiUniquePtr<char*[]> array_data_ptr = AllocJvmtiUniquePtr<char*[]>(env,
+                                                                        kPropertiesSize + 2,
+                                                                        &array_alloc_result);
+  if (array_data_ptr == nullptr) {
     return array_alloc_result;
   }
-  JvmtiUniquePtr array_data_ptr = MakeJvmtiUniquePtr(env, array_data);
-  char** array = reinterpret_cast<char**>(array_data);
 
-  std::vector<JvmtiUniquePtr> property_copies;
+  std::vector<JvmtiUniquePtr<char[]>> property_copies;
 
   {
-    char* libpath_data;
-    jvmtiError libpath_result = Copy(env, kPropertyLibraryPath, &libpath_data);
-    if (libpath_result != ERR(NONE)) {
+    jvmtiError libpath_result;
+    JvmtiUniquePtr<char[]> libpath_data = CopyString(env, kPropertyLibraryPath, &libpath_result);
+    if (libpath_data == nullptr) {
       return libpath_result;
     }
-    array[0] = libpath_data;
-    property_copies.push_back(MakeJvmtiUniquePtr(env, libpath_data));
+    array_data_ptr.get()[0] = libpath_data.get();
+    property_copies.push_back(std::move(libpath_data));
   }
 
   {
-    char* classpath_data;
-    jvmtiError classpath_result = Copy(env, kPropertyClassPath, &classpath_data);
-    if (classpath_result != ERR(NONE)) {
+    jvmtiError classpath_result;
+    JvmtiUniquePtr<char[]> classpath_data = CopyString(env, kPropertyClassPath, &classpath_result);
+    if (classpath_data == nullptr) {
       return classpath_result;
     }
-    array[1] = classpath_data;
-    property_copies.push_back(MakeJvmtiUniquePtr(env, classpath_data));
+    array_data_ptr.get()[1] = classpath_data.get();
+    property_copies.push_back(std::move(classpath_data));
   }
 
   for (size_t i = 0; i != kPropertiesSize; ++i) {
-    char* data;
-    jvmtiError data_result = Copy(env, kProperties[i][0], &data);
-    if (data_result != ERR(NONE)) {
+    jvmtiError data_result;
+    JvmtiUniquePtr<char[]> data = CopyString(env, kProperties[i][0], &data_result);
+    if (data == nullptr) {
       return data_result;
     }
-    array[i + 2] = data;
-    property_copies.push_back(MakeJvmtiUniquePtr(env, data));
+    array_data_ptr.get()[i + 2] = data.get();
+    property_copies.push_back(std::move(data));
   }
 
   // Everything is OK, release the data.
-  array_data_ptr.release();
+  *count_ptr = kPropertiesSize + 2;
+  *property_ptr = array_data_ptr.release();
   for (auto& uptr : property_copies) {
     uptr.release();
   }
 
-  *count_ptr = kPropertiesSize + 2;
-  *property_ptr = array;
-
   return ERR(NONE);
 }
 
+static jvmtiError Copy(jvmtiEnv* env, const char* in, char** out) {
+  jvmtiError result;
+  JvmtiUniquePtr<char[]> data = CopyString(env, in, &result);
+  *out = data.release();
+  return result;
+}
+
 jvmtiError PropertiesUtil::GetSystemProperty(jvmtiEnv* env,
                                              const char* property,
                                              char** value_ptr) {
diff --git a/runtime/openjdkjvmti/ti_redefine.cc b/runtime/openjdkjvmti/ti_redefine.cc
index 8436045..eefd012 100644
--- a/runtime/openjdkjvmti/ti_redefine.cc
+++ b/runtime/openjdkjvmti/ti_redefine.cc
@@ -170,10 +170,6 @@
       // We cannot ensure that the right dex file is used in inlined frames so we don't support
       // redefining them.
       DCHECK(!IsInInlinedFrame()) << "Inlined frames are not supported when using redefinition";
-      // TODO We should really support intrinsic obsolete methods.
-      // TODO We should really support redefining intrinsics.
-      // We don't support intrinsics so check for them here.
-      DCHECK(!old_method->IsIntrinsic());
       art::ArtMethod* new_obsolete_method = obsolete_maps_->FindObsoleteVersion(old_method);
       if (new_obsolete_method == nullptr) {
         // Create a new Obsolete Method and put it in the list.
@@ -241,6 +237,9 @@
   } else if (klass->IsInterface()) {
     *error_msg = "Modification of Interface classes is currently not supported";
     return ERR(UNMODIFIABLE_CLASS);
+  } else if (klass->IsStringClass()) {
+    *error_msg = "Modification of String class is not supported";
+    return ERR(UNMODIFIABLE_CLASS);
   } else if (klass->IsArrayClass()) {
     *error_msg = "Modification of Array classes is not supported";
     return ERR(UNMODIFIABLE_CLASS);
@@ -323,7 +322,6 @@
     // This makes cleanup easier (since we unambiguously own the bytes) and also is useful since we
     // will need to keep the original bytes around unaltered for subsequent RetransformClasses calls
     // to get the passed in bytes.
-    // TODO Implement saving the original bytes.
     unsigned char* class_bytes_copy = nullptr;
     jvmtiError res = env->Allocate(definitions[i].class_byte_count, &class_bytes_copy);
     if (res != OK) {
@@ -396,8 +394,8 @@
     *error_msg_ = "Unable to get class signature!";
     return ret;
   }
-  JvmtiUniquePtr generic_unique_ptr(MakeJvmtiUniquePtr(env, generic_ptr_unused));
-  JvmtiUniquePtr signature_unique_ptr(MakeJvmtiUniquePtr(env, signature_ptr));
+  JvmtiUniquePtr<char> generic_unique_ptr(MakeJvmtiUniquePtr(env, generic_ptr_unused));
+  JvmtiUniquePtr<char> signature_unique_ptr(MakeJvmtiUniquePtr(env, signature_ptr));
   std::unique_ptr<art::MemMap> map(MoveDataToMemMap(original_dex_location,
                                                     def.dex_len,
                                                     def.dex_data.get(),
@@ -518,6 +516,11 @@
   CallbackCtx ctx(&map, linker->GetAllocatorForClassLoader(art_klass->GetClassLoader()));
   // Add all the declared methods to the map
   for (auto& m : art_klass->GetDeclaredMethods(art::kRuntimePointerSize)) {
+    if (m.IsIntrinsic()) {
+      LOG(WARNING) << "Redefining intrinsic method " << m.PrettyMethod() << ". This may cause the "
+                   << "unexpected use of the original definition of " << m.PrettyMethod() << "in "
+                   << "methods that have already been compiled.";
+    }
     // It is possible to simply filter out some methods where they cannot really become obsolete,
     // such as native methods and keep their original (possibly optimized) implementations. We don't
     // do this, however, since we would need to mark these functions (still in the classes
@@ -526,8 +529,6 @@
     // error checking from the interpreter which ensure we don't try to start executing obsolete
     // methods.
     ctx.obsolete_methods.insert(&m);
-    // TODO Allow this or check in IsModifiableClass.
-    DCHECK(!m.IsIntrinsic());
   }
   {
     art::MutexLock mu(driver_->self_, *art::Locks::thread_list_lock_);
@@ -674,7 +675,6 @@
 }
 
 bool Redefiner::ClassRedefinition::CheckClass() {
-  // TODO Might just want to put it in a ObjPtr and NoSuspend assert.
   art::StackHandleScope<1> hs(driver_->self_);
   // Easy check that only 1 class def is present.
   if (dex_file_->NumClassDefs() != 1) {
@@ -750,7 +750,6 @@
   return true;
 }
 
-// TODO Move this to use IsRedefinable when that function is made.
 bool Redefiner::ClassRedefinition::CheckRedefinable() {
   std::string err;
   art::StackHandleScope<1> hs(driver_->self_);
@@ -883,7 +882,6 @@
   DISALLOW_COPY_AND_ASSIGN(RedefinitionDataHolder);
 };
 
-// TODO Stash and update soft failure state
 bool Redefiner::ClassRedefinition::CheckVerification(int32_t klass_index,
                                                      const RedefinitionDataHolder& holder) {
   DCHECK_EQ(dex_file_->NumClassDefs(), 1u);
@@ -976,7 +974,6 @@
         ClassLoaderHelper::FindSourceDexFileObject(driver_->self_, loader)));
     holder->SetJavaDexFile(klass_index, dex_file_obj.Get());
     if (dex_file_obj == nullptr) {
-      // TODO Better error msg.
       RecordFailure(ERR(INTERNAL), "Unable to find dex file!");
       return false;
     }
@@ -1124,11 +1121,6 @@
   self_->TransitionFromRunnableToSuspended(art::ThreadState::kNative);
   runtime_->GetThreadList()->SuspendAll(
       "Final installation of redefined Classes!", /*long_suspend*/true);
-  // TODO We need to invalidate all breakpoints in the redefined class with the debugger.
-  // TODO We need to deal with any instrumentation/debugger deoptimized_methods_.
-  // TODO We need to update all debugger MethodIDs so they note the method they point to is
-  // obsolete or implement some other well defined semantics.
-  // TODO We need to decide on & implement semantics for JNI jmethodids when we redefine methods.
   counter = 0;
   for (Redefiner::ClassRedefinition& redef : redefinitions_) {
     art::ScopedAssertNoThreadSuspension nts("Updating runtime objects for redefinition");
@@ -1143,6 +1135,10 @@
                       holder.GetOriginalDexFileBytes(counter));
     counter++;
   }
+  // TODO We should check for if any of the redefined methods are intrinsic methods here and, if any
+  // are, force a full-world deoptimization before finishing redefinition. If we don't do this then
+  // methods that have been jitted prior to the current redefinition being applied might continue
+  // to use the old versions of the intrinsics!
   // TODO Shrink the obsolete method maps if possible?
   // TODO Put this into a scoped thing.
   runtime_->GetThreadList()->ResumeAll();
@@ -1181,18 +1177,18 @@
     }
     const art::DexFile::ProtoId* proto_id = dex_file_->FindProtoId(method_return_idx,
                                                                    new_type_list);
-    // TODO Return false, cleanup.
     CHECK(proto_id != nullptr || old_type_list == nullptr);
     const art::DexFile::MethodId* method_id = dex_file_->FindMethodId(declaring_class_id,
                                                                       *new_name_id,
                                                                       *proto_id);
-    // TODO Return false, cleanup.
     CHECK(method_id != nullptr);
     uint32_t dex_method_idx = dex_file_->GetIndexForMethodId(*method_id);
     method.SetDexMethodIndex(dex_method_idx);
     linker->SetEntryPointsToInterpreter(&method);
     method.SetCodeItemOffset(dex_file_->FindCodeItemOffset(class_def, dex_method_idx));
     method.SetDexCacheResolvedMethods(new_dex_cache->GetResolvedMethods(), image_pointer_size);
+    // Clear all the intrinsics related flags.
+    method.ClearAccessFlags(art::kAccIntrinsic | (~art::kAccFlagsNotUsedByIntrinsic));
     // Notify the jit that this method is redefined.
     art::jit::Jit* jit = driver_->runtime_->GetJit();
     if (jit != nullptr) {
@@ -1210,7 +1206,6 @@
           dex_file_->FindTypeId(field.GetDeclaringClass()->GetDescriptor(&declaring_class_name));
       const art::DexFile::StringId* new_name_id = dex_file_->FindStringId(field.GetName());
       const art::DexFile::TypeId* new_type_id = dex_file_->FindTypeId(field.GetTypeDescriptor());
-      // TODO Handle error, cleanup.
       CHECK(new_name_id != nullptr && new_type_id != nullptr && new_declaring_id != nullptr);
       const art::DexFile::FieldId* new_field_id =
           dex_file_->FindFieldId(*new_declaring_id, *new_name_id, *new_type_id);
diff --git a/runtime/openjdkjvmti/ti_thread.cc b/runtime/openjdkjvmti/ti_thread.cc
index f8f8fa6..788ac30 100644
--- a/runtime/openjdkjvmti/ti_thread.cc
+++ b/runtime/openjdkjvmti/ti_thread.cc
@@ -186,17 +186,17 @@
     return ERR(INVALID_THREAD);
   }
 
-  JvmtiUniquePtr name_uptr;
+  JvmtiUniquePtr<char[]> name_uptr;
   if (self != nullptr) {
     // Have a native thread object, this thread is alive.
     std::string name;
     self->GetThreadName(name);
-    jvmtiError name_result = CopyString(
-        env, name.c_str(), reinterpret_cast<unsigned char**>(&info_ptr->name));
-    if (name_result != ERR(NONE)) {
+    jvmtiError name_result;
+    name_uptr = CopyString(env, name.c_str(), &name_result);
+    if (name_uptr == nullptr) {
       return name_result;
     }
-    name_uptr = MakeJvmtiUniquePtr(env, info_ptr->name);
+    info_ptr->name = name_uptr.get();
 
     info_ptr->priority = self->GetNativePriority();
 
@@ -239,12 +239,12 @@
       } else {
         name_cstr = "";
       }
-      jvmtiError name_result = CopyString(
-          env, name_cstr, reinterpret_cast<unsigned char**>(&info_ptr->name));
-      if (name_result != ERR(NONE)) {
+      jvmtiError name_result;
+      name_uptr = CopyString(env, name_cstr, &name_result);
+      if (name_uptr == nullptr) {
         return name_result;
       }
-      name_uptr = MakeJvmtiUniquePtr(env, info_ptr->name);
+      info_ptr->name = name_uptr.get();
     }
 
     // Priority.
diff --git a/runtime/openjdkjvmti/ti_threadgroup.cc b/runtime/openjdkjvmti/ti_threadgroup.cc
index 1423874..df14333 100644
--- a/runtime/openjdkjvmti/ti_threadgroup.cc
+++ b/runtime/openjdkjvmti/ti_threadgroup.cc
@@ -116,11 +116,12 @@
       tmp_str = name_obj->ToModifiedUtf8();
       tmp_cstr = tmp_str.c_str();
     }
-    jvmtiError result =
-        CopyString(env, tmp_cstr, reinterpret_cast<unsigned char**>(&info_ptr->name));
-    if (result != ERR(NONE)) {
+    jvmtiError result;
+    JvmtiUniquePtr<char[]> copy = CopyString(env, tmp_cstr, &result);
+    if (copy == nullptr) {
       return result;
     }
+    info_ptr->name = copy.release();
   }
 
   // Parent.
@@ -239,45 +240,38 @@
   std::vector<art::ObjPtr<art::mirror::Object>> thread_groups;
   GetChildThreadGroups(thread_group, &thread_groups);
 
-  jthread* thread_data = nullptr;
-  JvmtiUniquePtr peers_uptr;
+  JvmtiUniquePtr<jthread[]> peers_uptr;
   if (!thread_peers.empty()) {
-    unsigned char* data;
-    jvmtiError res = env->Allocate(sizeof(jthread) * thread_peers.size(), &data);
-    if (res != ERR(NONE)) {
+    jvmtiError res;
+    peers_uptr = AllocJvmtiUniquePtr<jthread[]>(env, thread_peers.size(), &res);
+    if (peers_uptr == nullptr) {
       return res;
     }
-    thread_data = reinterpret_cast<jthread*>(data);
-    peers_uptr = MakeJvmtiUniquePtr(env, data);
   }
 
-  jthreadGroup* group_data = nullptr;
+  JvmtiUniquePtr<jthreadGroup[]> group_uptr;
   if (!thread_groups.empty()) {
-    unsigned char* data;
-    jvmtiError res = env->Allocate(sizeof(jthreadGroup) * thread_groups.size(), &data);
-    if (res != ERR(NONE)) {
+    jvmtiError res;
+    group_uptr = AllocJvmtiUniquePtr<jthreadGroup[]>(env, thread_groups.size(), &res);
+    if (group_uptr == nullptr) {
       return res;
     }
-    group_data = reinterpret_cast<jthreadGroup*>(data);
   }
 
   // Can't fail anymore from here on.
 
   // Copy data into out buffers.
   for (size_t i = 0; i != thread_peers.size(); ++i) {
-    thread_data[i] = soa.AddLocalReference<jthread>(thread_peers[i]);
+    peers_uptr[i] = soa.AddLocalReference<jthread>(thread_peers[i]);
   }
   for (size_t i = 0; i != thread_groups.size(); ++i) {
-    group_data[i] = soa.AddLocalReference<jthreadGroup>(thread_groups[i]);
+    group_uptr[i] = soa.AddLocalReference<jthreadGroup>(thread_groups[i]);
   }
 
   *thread_count_ptr = static_cast<jint>(thread_peers.size());
-  *threads_ptr = thread_data;
+  *threads_ptr = peers_uptr.release();
   *group_count_ptr = static_cast<jint>(thread_groups.size());
-  *groups_ptr = group_data;
-
-  // Everything's fine.
-  peers_uptr.release();
+  *groups_ptr = group_uptr.release();
 
   return ERR(NONE);
 }
diff --git a/runtime/read_barrier-inl.h b/runtime/read_barrier-inl.h
index 37cf257..2b38b2e 100644
--- a/runtime/read_barrier-inl.h
+++ b/runtime/read_barrier-inl.h
@@ -198,7 +198,7 @@
 
 inline void ReadBarrier::AssertToSpaceInvariant(mirror::Object* obj, MemberOffset offset,
                                                 mirror::Object* ref) {
-  if (kEnableToSpaceInvariantChecks || kIsDebugBuild) {
+  if (kEnableToSpaceInvariantChecks) {
     if (ref == nullptr || IsDuringStartup()) {
       return;
     }
@@ -209,7 +209,7 @@
 
 inline void ReadBarrier::AssertToSpaceInvariant(GcRootSource* gc_root_source,
                                                 mirror::Object* ref) {
-  if (kEnableToSpaceInvariantChecks || kIsDebugBuild) {
+  if (kEnableToSpaceInvariantChecks) {
     if (ref == nullptr || IsDuringStartup()) {
       return;
     }
diff --git a/runtime/safe_map.h b/runtime/safe_map.h
index 49f80f3..e638fdb 100644
--- a/runtime/safe_map.h
+++ b/runtime/safe_map.h
@@ -137,6 +137,16 @@
     return it->second;
   }
 
+  iterator FindOrAdd(const K& k, const V& v) {
+    iterator it = find(k);
+    return it == end() ? Put(k, v) : it;
+  }
+
+  iterator FindOrAdd(const K& k) {
+    iterator it = find(k);
+    return it == end() ? Put(k, V()) : it;
+  }
+
   bool Equals(const Self& rhs) const {
     return map_ == rhs.map_;
   }
diff --git a/runtime/scoped_thread_state_change-inl.h b/runtime/scoped_thread_state_change-inl.h
index 000da59..c817a9e 100644
--- a/runtime/scoped_thread_state_change-inl.h
+++ b/runtime/scoped_thread_state_change-inl.h
@@ -79,11 +79,11 @@
   return obj == nullptr ? nullptr : Env()->AddLocalReference<T>(obj);
 }
 
-template<typename T, bool kPoison>
-inline ObjPtr<T, kPoison> ScopedObjectAccessAlreadyRunnable::Decode(jobject obj) const {
+template<typename T>
+inline ObjPtr<T> ScopedObjectAccessAlreadyRunnable::Decode(jobject obj) const {
   Locks::mutator_lock_->AssertSharedHeld(Self());
   DCHECK(IsRunnable());  // Don't work with raw objects in non-runnable states.
-  return ObjPtr<T, kPoison>::DownCast(Self()->DecodeJObject(obj));
+  return ObjPtr<T>::DownCast(Self()->DecodeJObject(obj));
 }
 
 inline bool ScopedObjectAccessAlreadyRunnable::IsRunnable() const {
diff --git a/runtime/scoped_thread_state_change.h b/runtime/scoped_thread_state_change.h
index 24199f7..a3286ac 100644
--- a/runtime/scoped_thread_state_change.h
+++ b/runtime/scoped_thread_state_change.h
@@ -27,7 +27,7 @@
 namespace art {
 
 struct JNIEnvExt;
-template<class MirrorType, bool kPoison> class ObjPtr;
+template<class MirrorType> class ObjPtr;
 
 // Scoped change into and out of a particular state. Handles Runnable transitions that require
 // more complicated suspension checking. The subclasses ScopedObjectAccessUnchecked and
@@ -91,8 +91,8 @@
   T AddLocalReference(ObjPtr<mirror::Object> obj) const
       REQUIRES_SHARED(Locks::mutator_lock_);
 
-  template<typename T, bool kPoison = kIsDebugBuild>
-  ObjPtr<T, kPoison> Decode(jobject obj) const REQUIRES_SHARED(Locks::mutator_lock_);
+  template<typename T>
+  ObjPtr<T> Decode(jobject obj) const REQUIRES_SHARED(Locks::mutator_lock_);
 
   ALWAYS_INLINE bool IsRunnable() const;
 
diff --git a/runtime/thread-inl.h b/runtime/thread-inl.h
index 8d94626..482e0e3 100644
--- a/runtime/thread-inl.h
+++ b/runtime/thread-inl.h
@@ -29,6 +29,7 @@
 #include "base/mutex-inl.h"
 #include "gc/heap.h"
 #include "jni_env_ext.h"
+#include "obj_ptr.h"
 #include "runtime.h"
 #include "thread_pool.h"
 
@@ -355,7 +356,7 @@
 }
 
 inline void Thread::PoisonObjectPointersIfDebug() {
-  if (kIsDebugBuild) {
+  if (kObjPtrPoisoning) {
     Thread::Current()->PoisonObjectPointers();
   }
 }
diff --git a/runtime/thread.cc b/runtime/thread.cc
index 3abb9fc..ff66cc1 100644
--- a/runtime/thread.cc
+++ b/runtime/thread.cc
@@ -2549,11 +2549,18 @@
         return nullptr;
       }
       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;
+      if (line_number == -1) {
+        // Make the line_number field of StackTraceElement 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 = dex_pc;
+      } else {
+        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;
+          }
         }
       }
     }
@@ -2564,11 +2571,11 @@
     if (method_name_object == nullptr) {
       return nullptr;
     }
-    ObjPtr<mirror::StackTraceElement> obj =mirror::StackTraceElement::Alloc(soa.Self(),
-                                                                            class_name_object,
-                                                                            method_name_object,
-                                                                            source_name_object,
-                                                                            line_number);
+    ObjPtr<mirror::StackTraceElement> obj = mirror::StackTraceElement::Alloc(soa.Self(),
+                                                                             class_name_object,
+                                                                             method_name_object,
+                                                                             source_name_object,
+                                                                             line_number);
     if (obj == nullptr) {
       return nullptr;
     }
diff --git a/runtime/utils/dex_cache_arrays_layout-inl.h b/runtime/utils/dex_cache_arrays_layout-inl.h
index 9865821..f9a1405 100644
--- a/runtime/utils/dex_cache_arrays_layout-inl.h
+++ b/runtime/utils/dex_cache_arrays_layout-inl.h
@@ -51,9 +51,11 @@
     : DexCacheArraysLayout(pointer_size, dex_file->GetHeader(), dex_file->NumCallSiteIds()) {
 }
 
-inline constexpr size_t DexCacheArraysLayout::Alignment() {
-  // GcRoot<> alignment is 4, i.e. lower than or equal to the pointer alignment.
-  static_assert(alignof(GcRoot<mirror::Class>) == 4, "Expecting alignof(GcRoot<>) == 4");
+constexpr size_t DexCacheArraysLayout::Alignment() {
+  // mirror::Type/String/MethodTypeDexCacheType alignment is 8,
+  // i.e. higher than or equal to the pointer alignment.
+  static_assert(alignof(mirror::TypeDexCacheType) == 8,
+                "Expecting alignof(ClassDexCacheType) == 8");
   static_assert(alignof(mirror::StringDexCacheType) == 8,
                 "Expecting alignof(StringDexCacheType) == 8");
   static_assert(alignof(mirror::MethodTypeDexCacheType) == 8,
@@ -63,17 +65,22 @@
 }
 
 template <typename T>
-static constexpr PointerSize GcRootAsPointerSize() {
+constexpr PointerSize GcRootAsPointerSize() {
   static_assert(sizeof(GcRoot<T>) == 4U, "Unexpected GcRoot size");
   return PointerSize::k32;
 }
 
 inline size_t DexCacheArraysLayout::TypeOffset(dex::TypeIndex type_idx) const {
-  return types_offset_ + ElementOffset(GcRootAsPointerSize<mirror::Class>(), type_idx.index_);
+  return types_offset_ + ElementOffset(PointerSize::k64,
+                                       type_idx.index_ % mirror::DexCache::kDexCacheTypeCacheSize);
 }
 
 inline size_t DexCacheArraysLayout::TypesSize(size_t num_elements) const {
-  return ArraySize(GcRootAsPointerSize<mirror::Class>(), num_elements);
+  size_t cache_size = mirror::DexCache::kDexCacheTypeCacheSize;
+  if (num_elements < cache_size) {
+    cache_size = num_elements;
+  }
+  return ArraySize(PointerSize::k64, cache_size);
 }
 
 inline size_t DexCacheArraysLayout::TypesAlignment() const {
diff --git a/runtime/verifier/method_verifier.cc b/runtime/verifier/method_verifier.cc
index 16739fa..38d151b 100644
--- a/runtime/verifier/method_verifier.cc
+++ b/runtime/verifier/method_verifier.cc
@@ -2399,7 +2399,8 @@
       const RegType& res_type = ResolveClassAndCheckAccess(type_idx);
       if (res_type.IsConflict()) {
         // If this is a primitive type, fail HARD.
-        mirror::Class* klass = dex_cache_->GetResolvedType(type_idx);
+        ObjPtr<mirror::Class> klass =
+            ClassLinker::LookupResolvedType(type_idx, dex_cache_.Get(), class_loader_.Get());
         if (klass != nullptr && klass->IsPrimitive()) {
           Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "using primitive type "
               << dex_file_->StringByTypeIdx(type_idx) << " in instanceof in "
@@ -3722,9 +3723,16 @@
 }
 
 const RegType& MethodVerifier::ResolveClassAndCheckAccess(dex::TypeIndex class_idx) {
-  mirror::Class* klass = dex_cache_->GetResolvedType(class_idx);
+  mirror::Class* klass = can_load_classes_
+      ? Runtime::Current()->GetClassLinker()->ResolveType(
+          *dex_file_, class_idx, dex_cache_, class_loader_)
+      : ClassLinker::LookupResolvedType(class_idx, dex_cache_.Get(), class_loader_.Get()).Ptr();
+  if (can_load_classes_ && klass == nullptr) {
+    DCHECK(self_->IsExceptionPending());
+    self_->ClearException();
+  }
   const RegType* result = nullptr;
-  if (klass != nullptr) {
+  if (klass != nullptr && !klass->IsErroneous()) {
     bool precise = klass->CannotBeAssignedFromOtherTypes();
     if (precise && !IsInstantiableOrPrimitive(klass)) {
       const char* descriptor = dex_file_->StringByTypeIdx(class_idx);
@@ -3747,10 +3755,6 @@
         << "' in " << GetDeclaringClass();
     return *result;
   }
-  if (klass == nullptr && !result->IsUnresolvedTypes()) {
-    klass = result->GetClass();
-    dex_cache_->SetResolvedType(class_idx, klass);
-  }
 
   // Record result of class resolution attempt.
   VerifierDeps::MaybeRecordClassResolution(*dex_file_, class_idx, klass);
diff --git a/test/100-reflect2/expected.txt b/test/100-reflect2/expected.txt
index dd89d64..e2a1001 100644
--- a/test/100-reflect2/expected.txt
+++ b/test/100-reflect2/expected.txt
@@ -33,7 +33,7 @@
 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), native void java.lang.String.setCharAt(int,char), private boolean java.lang.String.nonSyncContentEquals(java.lang.AbstractStringBuilder), private int java.lang.String.indexOfSupplementary(int,int), private int java.lang.String.lastIndexOfSupplementary(int,int), private native int java.lang.String.fastIndexOf(int,int), private native java.lang.String java.lang.String.fastSubstring(int,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.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.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.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 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(java.lang.String,java.lang.String,int), static int java.lang.String.lastIndexOf(char[],int,int,char[],int,int,int), static int java.lang.String.lastIndexOf(java.lang.String,java.lang.String,int), void java.lang.String.getChars(char[],int)]
+[native void java.lang.String.getCharsNoCheck(int,int,char[],int), private boolean java.lang.String.nonSyncContentEquals(java.lang.AbstractStringBuilder), private int java.lang.String.indexOfSupplementary(int,int), private int java.lang.String.lastIndexOfSupplementary(int,int), private native int java.lang.String.fastIndexOf(int,int), private native java.lang.String java.lang.String.doReplace(char,char), private native java.lang.String java.lang.String.fastSubstring(int,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.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.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.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 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(java.lang.String,java.lang.String,int), static int java.lang.String.lastIndexOf(char[],int,int,char[],int,int,int), static int java.lang.String.lastIndexOf(java.lang.String,java.lang.String,int), void java.lang.String.getChars(char[],int)]
 []
 [interface java.io.Serializable, interface java.lang.Comparable, interface java.lang.CharSequence]
 0
diff --git a/test/155-java-set-resolved-type/src/Main.java b/test/155-java-set-resolved-type/src/Main.java
index f92363e..56b8c3e 100644
--- a/test/155-java-set-resolved-type/src/Main.java
+++ b/test/155-java-set-resolved-type/src/Main.java
@@ -55,11 +55,7 @@
             Class<?> timpl = Class.forName("TestImplementation", false, mainLoader);
             // Clear the dex cache resolved types to force a proper lookup the next time
             // we need to find TestInterface.
-            // TODO: Enable clearing the dex cache when we switch to the hash-based type array
-            // and do a proper lookup. Currently, ClassLinker fully relies on the DexCache.
-            if (false) {
-                clearResolvedTypes(timpl);
-            }
+            clearResolvedTypes(timpl);
 
             // Force intialization of TestClass2. This expects the interface type to be
             // resolved and found through simple lookup.
diff --git a/test/201-built-in-except-detail-messages/src/Main.java b/test/201-built-in-except-detail-messages/src/Main.java
index dc58819..c2976c8 100644
--- a/test/201-built-in-except-detail-messages/src/Main.java
+++ b/test/201-built-in-except-detail-messages/src/Main.java
@@ -411,7 +411,7 @@
       m.invoke("hello", "world"); // Wrong type.
       fail();
     } catch (IllegalArgumentException iae) {
-      assertEquals("method java.lang.String.charAt! argument 1 has type int, got java.lang.String",
+      assertEquals("method java.lang.String.charAt argument 1 has type int, got java.lang.String",
           iae.getMessage());
     }
     try {
@@ -419,7 +419,7 @@
       m.invoke("hello", (Object) null); // Null for a primitive argument.
       fail();
     } catch (IllegalArgumentException iae) {
-      assertEquals("method java.lang.String.charAt! argument 1 has type int, got null",
+      assertEquals("method java.lang.String.charAt argument 1 has type int, got null",
           iae.getMessage());
     }
     try {
diff --git a/test/551-checker-shifter-operand/src/Main.java b/test/551-checker-shifter-operand/src/Main.java
index a4561b8..e967398 100644
--- a/test/551-checker-shifter-operand/src/Main.java
+++ b/test/551-checker-shifter-operand/src/Main.java
@@ -76,6 +76,25 @@
    * the shifter operand.
    */
 
+  /// CHECK-START-ARM: long Main.$opt$noinline$translate(long, byte) instruction_simplifier_arm (before)
+  /// CHECK-DAG:   <<l:j\d+>>           ParameterValue
+  /// CHECK-DAG:   <<b:b\d+>>           ParameterValue
+  /// CHECK:       <<tmp:j\d+>>         TypeConversion [<<b>>]
+  /// CHECK:                            Sub [<<l>>,<<tmp>>]
+
+  /// CHECK-START-ARM: long Main.$opt$noinline$translate(long, byte) instruction_simplifier_arm (after)
+  /// CHECK-DAG:   <<l:j\d+>>           ParameterValue
+  /// CHECK-DAG:   <<b:b\d+>>           ParameterValue
+  /// CHECK:                            DataProcWithShifterOp [<<l>>,<<b>>] kind:Sub+SXTB
+
+  /// CHECK-START-ARM: long Main.$opt$noinline$translate(long, byte) instruction_simplifier_arm (after)
+  /// CHECK-NOT:                        TypeConversion
+  /// CHECK-NOT:                        Sub
+
+  /// CHECK-START-ARM: long Main.$opt$noinline$translate(long, byte) disassembly (after)
+  /// CHECK:                            subs r{{\d+}}, r{{\d+}}, r{{\d+}}
+  /// CHECK:                            sbc r{{\d+}}, r{{\d+}}, r{{\d+}}, asr #31
+
   /// CHECK-START-ARM64: long Main.$opt$noinline$translate(long, byte) instruction_simplifier_arm64 (before)
   /// CHECK-DAG:   <<l:j\d+>>           ParameterValue
   /// CHECK-DAG:   <<b:b\d+>>           ParameterValue
@@ -85,7 +104,7 @@
   /// CHECK-START-ARM64: long Main.$opt$noinline$translate(long, byte) instruction_simplifier_arm64 (after)
   /// CHECK-DAG:   <<l:j\d+>>           ParameterValue
   /// CHECK-DAG:   <<b:b\d+>>           ParameterValue
-  /// CHECK:                            Arm64DataProcWithShifterOp [<<l>>,<<b>>] kind:Sub+SXTB
+  /// CHECK:                            DataProcWithShifterOp [<<l>>,<<b>>] kind:Sub+SXTB
 
   /// CHECK-START-ARM64: long Main.$opt$noinline$translate(long, byte) instruction_simplifier_arm64 (after)
   /// CHECK-NOT:                        TypeConversion
@@ -106,6 +125,21 @@
    * inputs are the the IR.
    */
 
+  /// CHECK-START-ARM: int Main.$opt$noinline$sameInput(int) instruction_simplifier_arm (before)
+  /// CHECK:       <<a:i\d+>>           ParameterValue
+  /// CHECK:       <<Const2:i\d+>>      IntConstant 2
+  /// CHECK:       <<tmp:i\d+>>         Shl [<<a>>,<<Const2>>]
+  /// CHECK:                            Add [<<tmp>>,<<tmp>>]
+
+  /// CHECK-START-ARM: int Main.$opt$noinline$sameInput(int) instruction_simplifier_arm (after)
+  /// CHECK-DAG:   <<a:i\d+>>           ParameterValue
+  /// CHECK-DAG:   <<Const2:i\d+>>      IntConstant 2
+  /// CHECK:       <<Shl:i\d+>>         Shl [<<a>>,<<Const2>>]
+  /// CHECK:                            Add [<<Shl>>,<<Shl>>]
+
+  /// CHECK-START-ARM: int Main.$opt$noinline$sameInput(int) instruction_simplifier_arm (after)
+  /// CHECK-NOT:                        DataProcWithShifterOp
+
   /// CHECK-START-ARM64: int Main.$opt$noinline$sameInput(int) instruction_simplifier_arm64 (before)
   /// CHECK:       <<a:i\d+>>           ParameterValue
   /// CHECK:       <<Const2:i\d+>>      IntConstant 2
@@ -119,7 +153,7 @@
   /// CHECK:                            Add [<<Shl>>,<<Shl>>]
 
   /// CHECK-START-ARM64: int Main.$opt$noinline$sameInput(int) instruction_simplifier_arm64 (after)
-  /// CHECK-NOT:                        Arm64DataProcWithShifterOp
+  /// CHECK-NOT:                        DataProcWithShifterOp
 
   public static int $opt$noinline$sameInput(int a) {
     if (doThrow) throw new Error();
@@ -131,6 +165,28 @@
    * Check that we perform the merge for multiple uses.
    */
 
+  /// CHECK-START-ARM: int Main.$opt$noinline$multipleUses(int) instruction_simplifier_arm (before)
+  /// CHECK:       <<arg:i\d+>>         ParameterValue
+  /// CHECK:       <<Const23:i\d+>>     IntConstant 23
+  /// CHECK:       <<tmp:i\d+>>         Shl [<<arg>>,<<Const23>>]
+  /// CHECK:                            Add [<<tmp>>,{{i\d+}}]
+  /// CHECK:                            Add [<<tmp>>,{{i\d+}}]
+  /// CHECK:                            Add [<<tmp>>,{{i\d+}}]
+  /// CHECK:                            Add [<<tmp>>,{{i\d+}}]
+  /// CHECK:                            Add [<<tmp>>,{{i\d+}}]
+
+  /// CHECK-START-ARM: int Main.$opt$noinline$multipleUses(int) instruction_simplifier_arm (after)
+  /// CHECK:       <<arg:i\d+>>         ParameterValue
+  /// CHECK:                            DataProcWithShifterOp [{{i\d+}},<<arg>>] kind:Add+LSL shift:23
+  /// CHECK:                            DataProcWithShifterOp [{{i\d+}},<<arg>>] kind:Add+LSL shift:23
+  /// CHECK:                            DataProcWithShifterOp [{{i\d+}},<<arg>>] kind:Add+LSL shift:23
+  /// CHECK:                            DataProcWithShifterOp [{{i\d+}},<<arg>>] kind:Add+LSL shift:23
+  /// CHECK:                            DataProcWithShifterOp [{{i\d+}},<<arg>>] kind:Add+LSL shift:23
+
+  /// CHECK-START-ARM: int Main.$opt$noinline$multipleUses(int) instruction_simplifier_arm (after)
+  /// CHECK-NOT:                        Shl
+  /// CHECK-NOT:                        Add
+
   /// CHECK-START-ARM64: int Main.$opt$noinline$multipleUses(int) instruction_simplifier_arm64 (before)
   /// CHECK:       <<arg:i\d+>>         ParameterValue
   /// CHECK:       <<Const23:i\d+>>     IntConstant 23
@@ -143,11 +199,11 @@
 
   /// CHECK-START-ARM64: int Main.$opt$noinline$multipleUses(int) instruction_simplifier_arm64 (after)
   /// CHECK:       <<arg:i\d+>>         ParameterValue
-  /// CHECK:                            Arm64DataProcWithShifterOp [{{i\d+}},<<arg>>] kind:Add+LSL shift:23
-  /// CHECK:                            Arm64DataProcWithShifterOp [{{i\d+}},<<arg>>] kind:Add+LSL shift:23
-  /// CHECK:                            Arm64DataProcWithShifterOp [{{i\d+}},<<arg>>] kind:Add+LSL shift:23
-  /// CHECK:                            Arm64DataProcWithShifterOp [{{i\d+}},<<arg>>] kind:Add+LSL shift:23
-  /// CHECK:                            Arm64DataProcWithShifterOp [{{i\d+}},<<arg>>] kind:Add+LSL shift:23
+  /// CHECK:                            DataProcWithShifterOp [{{i\d+}},<<arg>>] kind:Add+LSL shift:23
+  /// CHECK:                            DataProcWithShifterOp [{{i\d+}},<<arg>>] kind:Add+LSL shift:23
+  /// CHECK:                            DataProcWithShifterOp [{{i\d+}},<<arg>>] kind:Add+LSL shift:23
+  /// CHECK:                            DataProcWithShifterOp [{{i\d+}},<<arg>>] kind:Add+LSL shift:23
+  /// CHECK:                            DataProcWithShifterOp [{{i\d+}},<<arg>>] kind:Add+LSL shift:23
 
   /// CHECK-START-ARM64: int Main.$opt$noinline$multipleUses(int) instruction_simplifier_arm64 (after)
   /// CHECK-NOT:                        Shl
@@ -171,9 +227,19 @@
    * operand, so test that only the shifts are merged.
    */
 
+  /// CHECK-START-ARM: void Main.$opt$noinline$testAnd(long, long) instruction_simplifier_arm (after)
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK-NOT:                        DataProcWithShifterOp
+
+  /// CHECK-START-ARM: void Main.$opt$noinline$testAnd(long, long) disassembly (after)
+  /// CHECK:                            and lsl
+  /// CHECK:                            sbfx
+  /// CHECK:                            asr
+  /// CHECK:                            and
+
   /// CHECK-START-ARM64: void Main.$opt$noinline$testAnd(long, long) instruction_simplifier_arm64 (after)
-  /// CHECK:                            Arm64DataProcWithShifterOp
-  /// CHECK-NOT:                        Arm64DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK-NOT:                        DataProcWithShifterOp
 
   /// CHECK-START-ARM64: void Main.$opt$noinline$testAnd(long, long) disassembly (after)
   /// CHECK:                            and lsl
@@ -186,9 +252,18 @@
                      (a & (b << 5)) | (a & (byte)b));
   }
 
+  /// CHECK-START-ARM: void Main.$opt$noinline$testOr(int, int) instruction_simplifier_arm (after)
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK-NOT:                        DataProcWithShifterOp
+
+  /// CHECK-START-ARM: void Main.$opt$noinline$testOr(int, int) disassembly (after)
+  /// CHECK:                            orr asr
+  /// CHECK:                            ubfx
+  /// CHECK:                            orr
+
   /// CHECK-START-ARM64: void Main.$opt$noinline$testOr(int, int) instruction_simplifier_arm64 (after)
-  /// CHECK:                            Arm64DataProcWithShifterOp
-  /// CHECK-NOT:                        Arm64DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK-NOT:                        DataProcWithShifterOp
 
   /// CHECK-START-ARM64: void Main.$opt$noinline$testOr(int, int) disassembly (after)
   /// CHECK:                            orr asr
@@ -201,9 +276,19 @@
                     (a | (b >> 6)) | (a | (char)b));
   }
 
+  /// CHECK-START-ARM: void Main.$opt$noinline$testXor(long, long) instruction_simplifier_arm (after)
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK-NOT:                        DataProcWithShifterOp
+
+  /// CHECK-START-ARM: void Main.$opt$noinline$testXor(long, long) disassembly (after)
+  /// CHECK:                            eor lsr
+  /// CHECK:                            mov
+  /// CHECK:                            asr
+  /// CHECK:                            eor
+
   /// CHECK-START-ARM64: void Main.$opt$noinline$testXor(long, long) instruction_simplifier_arm64 (after)
-  /// CHECK:                            Arm64DataProcWithShifterOp
-  /// CHECK-NOT:                        Arm64DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK-NOT:                        DataProcWithShifterOp
 
   /// CHECK-START-ARM64: void Main.$opt$noinline$testXor(long, long) disassembly (after)
   /// CHECK:                            eor lsr
@@ -216,9 +301,12 @@
                      (a ^ (b >>> 7)) | (a ^ (int)b));
   }
 
+  /// CHECK-START-ARM: void Main.$opt$noinline$testNeg(int) instruction_simplifier_arm (after)
+  /// CHECK-NOT:                            DataProcWithShifterOp
+
   /// CHECK-START-ARM64: void Main.$opt$noinline$testNeg(int) instruction_simplifier_arm64 (after)
-  /// CHECK:                            Arm64DataProcWithShifterOp
-  /// CHECK-NOT:                        Arm64DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK-NOT:                        DataProcWithShifterOp
 
   /// CHECK-START-ARM64: void Main.$opt$noinline$testNeg(int) disassembly (after)
   /// CHECK:                            neg lsl
@@ -239,9 +327,12 @@
    * does occur on the right-hand.
    */
 
+  /// CHECK-START-ARM: void Main.$opt$validateExtendByteInt1(int, byte) instruction_simplifier_arm (after)
+  /// CHECK-NOT:                        DataProcWithShifterOp
+
   /// CHECK-START-ARM64: void Main.$opt$validateExtendByteInt1(int, byte) instruction_simplifier_arm64 (after)
-  /// CHECK:                            Arm64DataProcWithShifterOp
-  /// CHECK-NOT:                        Arm64DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK-NOT:                        DataProcWithShifterOp
 
   /// CHECK-START-ARM64: void Main.$opt$validateExtendByteInt1(int, byte) instruction_simplifier_arm64 (after)
   /// CHECK-NOT:                        TypeConversion
@@ -252,9 +343,11 @@
     assertIntEquals(a + $noinline$byteToShort(b), a + (short)b);
   }
 
+  /// CHECK-START-ARM: void Main.$opt$validateExtendByteInt2(int, byte) instruction_simplifier_arm (after)
+  /// CHECK-NOT:                        DataProcWithShifterOp
+
   /// CHECK-START-ARM64: void Main.$opt$validateExtendByteInt2(int, byte) instruction_simplifier_arm64 (after)
-  /// CHECK-NOT:                        Arm64DataProcWithShifterOp
-  /// CHECK-NOT:                        Arm64DataProcWithShifterOp
+  /// CHECK-NOT:                        DataProcWithShifterOp
 
   public static void $opt$validateExtendByteInt2(int a, byte b) {
     // The conversion to `int` has been optimized away, so there is nothing to merge.
@@ -263,13 +356,25 @@
     assertLongEquals(a + $noinline$byteToLong(b), a + (long)b);
   }
 
+  /// CHECK-START-ARM: void Main.$opt$validateExtendByteLong(long, byte) instruction_simplifier_arm (after)
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK-NOT:                        DataProcWithShifterOp
+
+  /// CHECK-START-ARM: void Main.$opt$validateExtendByteLong(long, byte) instruction_simplifier_arm (after)
+  /// CHECK:                            TypeConversion
+  /// CHECK-NOT:                        TypeConversion
+
   /// CHECK-START-ARM64: void Main.$opt$validateExtendByteLong(long, byte) instruction_simplifier_arm64 (after)
-  /// CHECK:                            Arm64DataProcWithShifterOp
-  /// CHECK:                            Arm64DataProcWithShifterOp
-  /// CHECK:                            Arm64DataProcWithShifterOp
-  /// CHECK:                            Arm64DataProcWithShifterOp
-  /// CHECK:                            Arm64DataProcWithShifterOp
-  /// CHECK-NOT:                        Arm64DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK-NOT:                        DataProcWithShifterOp
 
   /// CHECK-START-ARM64: void Main.$opt$validateExtendByteLong(long, byte) instruction_simplifier_arm64 (after)
   /// CHECK:                            TypeConversion
@@ -294,9 +399,12 @@
     $opt$validateExtendByteLong(a, b);
   }
 
+  /// CHECK-START-ARM: void Main.$opt$validateExtendCharInt1(int, char) instruction_simplifier_arm (after)
+  /// CHECK-NOT:                        DataProcWithShifterOp
+
   /// CHECK-START-ARM64: void Main.$opt$validateExtendCharInt1(int, char) instruction_simplifier_arm64 (after)
-  /// CHECK:                            Arm64DataProcWithShifterOp
-  /// CHECK:                            Arm64DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
 
   /// CHECK-START-ARM64: void Main.$opt$validateExtendCharInt1(int, char) instruction_simplifier_arm64 (after)
   /// CHECK-NOT:                        TypeConversion
@@ -306,22 +414,41 @@
     assertIntEquals(a + $noinline$charToShort(b), a + (short)b);
   }
 
+  /// CHECK-START-ARM: void Main.$opt$validateExtendCharInt2(int, char) instruction_simplifier_arm (after)
+  /// CHECK-NOT:                        DataProcWithShifterOp
+
   /// CHECK-START-ARM64: void Main.$opt$validateExtendCharInt2(int, char) instruction_simplifier_arm64 (after)
-  /// CHECK-NOT:                        Arm64DataProcWithShifterOp
-  /// CHECK-NOT:                        Arm64DataProcWithShifterOp
+  /// CHECK-NOT:                        DataProcWithShifterOp
 
   public static void $opt$validateExtendCharInt2(int a, char b) {
     // The conversion to `int` has been optimized away, so there is nothing to merge.
     assertIntEquals (a + $noinline$charToInt (b), a +  (int)b);
-    // There is an environment use for `(long)b`, preventing the merge.
+    // There is an environment use for `(long)b` and the implicit `(long)a`, preventing the merge.
     assertLongEquals(a + $noinline$charToLong(b), a + (long)b);
   }
 
+  /// CHECK-START-ARM: void Main.$opt$validateExtendCharLong(long, char) instruction_simplifier_arm (after)
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK-NOT:                        DataProcWithShifterOp
+
+  /// CHECK-START-ARM: void Main.$opt$validateExtendCharLong(long, char) instruction_simplifier_arm (after)
+  /// CHECK:                            TypeConversion
+  /// CHECK:                            TypeConversion
+  /// CHECK-NOT:                        TypeConversion
+
   /// CHECK-START-ARM64: void Main.$opt$validateExtendCharLong(long, char) instruction_simplifier_arm64 (after)
-  /// CHECK:                            Arm64DataProcWithShifterOp
-  /// CHECK:                            Arm64DataProcWithShifterOp
-  /// CHECK:                            Arm64DataProcWithShifterOp
-  /// CHECK:                            Arm64DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK-NOT:                        DataProcWithShifterOp
 
   /// CHECK-START-ARM64: void Main.$opt$validateExtendCharLong(long, char) instruction_simplifier_arm64 (after)
   /// CHECK:                            TypeConversion
@@ -332,7 +459,7 @@
     // The first two tests have a type conversion.
     assertLongEquals(a + $noinline$charToByte (b), a +  (byte)b);
     assertLongEquals(a + $noinline$charToShort(b), a + (short)b);
-    // This test does not because the conversion to `int` is optimized away.
+    // On ARM64 this test does not because the conversion to `int` is optimized away.
     assertLongEquals(a + $noinline$charToInt  (b), a +   (int)b);
   }
 
@@ -342,9 +469,12 @@
     $opt$validateExtendCharLong(a, b);
   }
 
+  /// CHECK-START-ARM: void Main.$opt$validateExtendShortInt1(int, short) instruction_simplifier_arm (after)
+  /// CHECK-NOT:                        DataProcWithShifterOp
+
   /// CHECK-START-ARM64: void Main.$opt$validateExtendShortInt1(int, short) instruction_simplifier_arm64 (after)
-  /// CHECK:                            Arm64DataProcWithShifterOp
-  /// CHECK:                            Arm64DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
 
   /// CHECK-START-ARM64: void Main.$opt$validateExtendShortInt1(int, short) instruction_simplifier_arm64 (after)
   /// CHECK-NOT:                        TypeConversion
@@ -354,21 +484,41 @@
     assertIntEquals(a + $noinline$shortToChar (b), a + (char)b);
   }
 
+  /// CHECK-START-ARM: void Main.$opt$validateExtendShortInt2(int, short) instruction_simplifier_arm (after)
+  /// CHECK-NOT:                        DataProcWithShifterOp
+
   /// CHECK-START-ARM64: void Main.$opt$validateExtendShortInt2(int, short) instruction_simplifier_arm64 (after)
-  /// CHECK-NOT:                        Arm64DataProcWithShifterOp
-  /// CHECK-NOT:                        Arm64DataProcWithShifterOp
+  /// CHECK-NOT:                        DataProcWithShifterOp
 
   public static void $opt$validateExtendShortInt2(int a, short b) {
     // The conversion to `int` has been optimized away, so there is nothing to merge.
     assertIntEquals (a + $noinline$shortToInt  (b), a +  (int)b);
-    // There is an environment use for `(long)b`, preventing the merge.
+    // There is an environment use for `(long)b` and the implicit `(long)a`, preventing the merge.
     assertLongEquals(a + $noinline$shortToLong (b), a + (long)b);
   }
 
+  /// CHECK-START-ARM: void Main.$opt$validateExtendShortLong(long, short) instruction_simplifier_arm (after)
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK-NOT:                        DataProcWithShifterOp
+
+  /// CHECK-START-ARM: void Main.$opt$validateExtendShortLong(long, short) instruction_simplifier_arm (after)
+  /// CHECK:                            TypeConversion
+  /// CHECK:                            TypeConversion
+  /// CHECK-NOT:                        TypeConversion
+
   /// CHECK-START-ARM64: void Main.$opt$validateExtendShortLong(long, short) instruction_simplifier_arm64 (after)
-  /// CHECK:                            Arm64DataProcWithShifterOp
-  /// CHECK:                            Arm64DataProcWithShifterOp
-  /// CHECK:                            Arm64DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK-NOT:                        DataProcWithShifterOp
 
   /// CHECK-START-ARM64: void Main.$opt$validateExtendShortLong(long, short) instruction_simplifier_arm64 (after)
   /// CHECK:                            TypeConversion
@@ -379,7 +529,7 @@
     // The first two tests have a type conversion.
     assertLongEquals(a + $noinline$shortToByte(b), a + (byte)b);
     assertLongEquals(a + $noinline$shortToChar(b), a + (char)b);
-    // This test does not because the conversion to `int` is optimized away.
+    // On ARM64 this test does not because the conversion to `int` is optimized away.
     assertLongEquals(a + $noinline$shortToInt (b), a +  (int)b);
   }
 
@@ -389,11 +539,31 @@
     $opt$validateExtendShortLong(a, b);
   }
 
+  /// CHECK-START-ARM: void Main.$opt$validateExtendInt(long, int) instruction_simplifier_arm (after)
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK-NOT:                        DataProcWithShifterOp
+
+  /// CHECK-START-ARM: void Main.$opt$validateExtendInt(long, int) instruction_simplifier_arm (after)
+  /// CHECK:                            TypeConversion
+  /// CHECK:                            TypeConversion
+  /// CHECK:                            TypeConversion
+  /// CHECK-NOT:                        TypeConversion
+
   /// CHECK-START-ARM64: void Main.$opt$validateExtendInt(long, int) instruction_simplifier_arm64 (after)
-  /// CHECK:                            Arm64DataProcWithShifterOp
-  /// CHECK:                            Arm64DataProcWithShifterOp
-  /// CHECK:                            Arm64DataProcWithShifterOp
-  /// CHECK:                            Arm64DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK-NOT:                        DataProcWithShifterOp
 
   /// CHECK-START-ARM64: void Main.$opt$validateExtendInt(long, int) instruction_simplifier_arm64 (after)
   /// CHECK:                            TypeConversion
@@ -411,11 +581,34 @@
     assertLongEquals(a + $noinline$intToLong (b), a +  (long)b);
   }
 
+  /// CHECK-START-ARM: void Main.$opt$validateExtendLong(long, long) instruction_simplifier_arm (after)
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK-NOT:                        DataProcWithShifterOp
+
+  /// CHECK-START-ARM: void Main.$opt$validateExtendLong(long, long) instruction_simplifier_arm (after)
+  /// CHECK:                            TypeConversion
+  /// CHECK:                            TypeConversion
+  /// CHECK:                            TypeConversion
+  /// CHECK:                            TypeConversion
+  /// CHECK-NOT:                        TypeConversion
+
   /// CHECK-START-ARM64: void Main.$opt$validateExtendLong(long, long) instruction_simplifier_arm64 (after)
-  /// CHECK:                            Arm64DataProcWithShifterOp
-  /// CHECK:                            Arm64DataProcWithShifterOp
-  /// CHECK:                            Arm64DataProcWithShifterOp
-  /// CHECK:                            Arm64DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK-NOT:                        DataProcWithShifterOp
 
   /// CHECK-START-ARM64: void Main.$opt$validateExtendLong(long, long) instruction_simplifier_arm64 (after)
   /// CHECK:                            TypeConversion
@@ -449,40 +642,83 @@
 
 
   // Each test line below should see one merge.
+  /// CHECK-START-ARM: void Main.$opt$validateShiftInt(int, int) instruction_simplifier_arm (after)
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK-NOT:                        DataProcWithShifterOp
+  // Note: `b << 32`, `b >> 32` and `b >>> 32` are optimized away by generic simplifier.
+
+  /// CHECK-START-ARM: void Main.$opt$validateShiftInt(int, int) instruction_simplifier_arm (after)
+  /// CHECK-NOT:                        Shl
+  /// CHECK-NOT:                        Shr
+  /// CHECK-NOT:                        UShr
+
   /// CHECK-START-ARM64: void Main.$opt$validateShiftInt(int, int) instruction_simplifier_arm64 (after)
-  /// CHECK:                            Arm64DataProcWithShifterOp
-  /// CHECK:                            Arm64DataProcWithShifterOp
-  /// CHECK:                            Arm64DataProcWithShifterOp
-  /// CHECK:                            Arm64DataProcWithShifterOp
-  /// CHECK:                            Arm64DataProcWithShifterOp
-  /// CHECK:                            Arm64DataProcWithShifterOp
-  /// CHECK:                            Arm64DataProcWithShifterOp
-  /// CHECK:                            Arm64DataProcWithShifterOp
-  /// CHECK:                            Arm64DataProcWithShifterOp
-  /// CHECK:                            Arm64DataProcWithShifterOp
-  /// CHECK:                            Arm64DataProcWithShifterOp
-  /// CHECK:                            Arm64DataProcWithShifterOp
-  /// CHECK:                            Arm64DataProcWithShifterOp
-  /// CHECK:                            Arm64DataProcWithShifterOp
-  /// CHECK:                            Arm64DataProcWithShifterOp
-  /// CHECK:                            Arm64DataProcWithShifterOp
-  /// CHECK:                            Arm64DataProcWithShifterOp
-  /// CHECK:                            Arm64DataProcWithShifterOp
-  /// CHECK:                            Arm64DataProcWithShifterOp
-  /// CHECK:                            Arm64DataProcWithShifterOp
-  /// CHECK:                            Arm64DataProcWithShifterOp
-  /// CHECK:                            Arm64DataProcWithShifterOp
-  /// CHECK:                            Arm64DataProcWithShifterOp
-  /// CHECK:                            Arm64DataProcWithShifterOp
-  /// CHECK:                            Arm64DataProcWithShifterOp
-  /// CHECK:                            Arm64DataProcWithShifterOp
-  /// CHECK:                            Arm64DataProcWithShifterOp
-  /// CHECK:                            Arm64DataProcWithShifterOp
-  /// CHECK:                            Arm64DataProcWithShifterOp
-  /// CHECK:                            Arm64DataProcWithShifterOp
-  /// CHECK:                            Arm64DataProcWithShifterOp
-  /// CHECK:                            Arm64DataProcWithShifterOp
-  /// CHECK:                            Arm64DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK-NOT:                        DataProcWithShifterOp
   // Note: `b << 32`, `b >> 32` and `b >>> 32` are optimized away by generic simplifier.
 
   /// CHECK-START-ARM64: void Main.$opt$validateShiftInt(int, int) instruction_simplifier_arm64 (after)
@@ -552,43 +788,89 @@
   }
 
   // Each test line below should see one merge.
+  /// CHECK-START-ARM: void Main.$opt$validateShiftLong(long, long) instruction_simplifier_arm (after)
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK-NOT:                        DataProcWithShifterOp
+
+  // On ARM shifts by 1 are not merged.
+  /// CHECK-START-ARM: void Main.$opt$validateShiftLong(long, long) instruction_simplifier_arm (after)
+  /// CHECK:                            Shl
+  /// CHECK-NOT:                        Shl
+  /// CHECK:                            Shr
+  /// CHECK-NOT:                        Shr
+  /// CHECK:                            UShr
+  /// CHECK-NOT:                        UShr
+
   /// CHECK-START-ARM64: void Main.$opt$validateShiftLong(long, long) instruction_simplifier_arm64 (after)
-  /// CHECK:                            Arm64DataProcWithShifterOp
-  /// CHECK:                            Arm64DataProcWithShifterOp
-  /// CHECK:                            Arm64DataProcWithShifterOp
-  /// CHECK:                            Arm64DataProcWithShifterOp
-  /// CHECK:                            Arm64DataProcWithShifterOp
-  /// CHECK:                            Arm64DataProcWithShifterOp
-  /// CHECK:                            Arm64DataProcWithShifterOp
-  /// CHECK:                            Arm64DataProcWithShifterOp
-  /// CHECK:                            Arm64DataProcWithShifterOp
-  /// CHECK:                            Arm64DataProcWithShifterOp
-  /// CHECK:                            Arm64DataProcWithShifterOp
-  /// CHECK:                            Arm64DataProcWithShifterOp
-  /// CHECK:                            Arm64DataProcWithShifterOp
-  /// CHECK:                            Arm64DataProcWithShifterOp
-  /// CHECK:                            Arm64DataProcWithShifterOp
-  /// CHECK:                            Arm64DataProcWithShifterOp
-  /// CHECK:                            Arm64DataProcWithShifterOp
-  /// CHECK:                            Arm64DataProcWithShifterOp
-  /// CHECK:                            Arm64DataProcWithShifterOp
-  /// CHECK:                            Arm64DataProcWithShifterOp
-  /// CHECK:                            Arm64DataProcWithShifterOp
-  /// CHECK:                            Arm64DataProcWithShifterOp
-  /// CHECK:                            Arm64DataProcWithShifterOp
-  /// CHECK:                            Arm64DataProcWithShifterOp
-  /// CHECK:                            Arm64DataProcWithShifterOp
-  /// CHECK:                            Arm64DataProcWithShifterOp
-  /// CHECK:                            Arm64DataProcWithShifterOp
-  /// CHECK:                            Arm64DataProcWithShifterOp
-  /// CHECK:                            Arm64DataProcWithShifterOp
-  /// CHECK:                            Arm64DataProcWithShifterOp
-  /// CHECK:                            Arm64DataProcWithShifterOp
-  /// CHECK:                            Arm64DataProcWithShifterOp
-  /// CHECK:                            Arm64DataProcWithShifterOp
-  /// CHECK:                            Arm64DataProcWithShifterOp
-  /// CHECK:                            Arm64DataProcWithShifterOp
-  /// CHECK:                            Arm64DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK:                            DataProcWithShifterOp
+  /// CHECK-NOT:                        DataProcWithShifterOp
 
   /// CHECK-START-ARM64: void Main.$opt$validateShiftLong(long, long) instruction_simplifier_arm64 (after)
   /// CHECK-NOT:                        Shl
diff --git a/test/593-checker-shift-and-simplifier/src/Main.java b/test/593-checker-shift-and-simplifier/src/Main.java
index 65e809a..c9826bc 100644
--- a/test/593-checker-shift-and-simplifier/src/Main.java
+++ b/test/593-checker-shift-and-simplifier/src/Main.java
@@ -21,6 +21,17 @@
   // A very particular set of operations that caused a double removal by the
   // ARM64 simplifier doing "forward" removals (b/27851582).
 
+  /// CHECK-START-ARM: int Main.operations() instruction_simplifier_arm (before)
+  /// CHECK-DAG: <<Get:i\d+>> ArrayGet
+  /// CHECK-DAG: <<Not:i\d+>> Not [<<Get>>]
+  /// CHECK-DAG: <<Shl:i\d+>> Shl [<<Get>>,i{{\d+}}]
+  /// CHECK-DAG:              And [<<Not>>,<<Shl>>]
+  //
+  /// CHECK-START-ARM: int Main.operations() instruction_simplifier_arm (after)
+  /// CHECK-DAG: <<Get:i\d+>> ArrayGet
+  /// CHECK-DAG: <<Not:i\d+>> Not [<<Get>>]
+  /// CHECK-DAG:              DataProcWithShifterOp [<<Not>>,<<Get>>] kind:And+LSL shift:2
+
   /// CHECK-START-ARM64: int Main.operations() instruction_simplifier_arm64 (before)
   /// CHECK-DAG: <<Get:i\d+>> ArrayGet
   /// CHECK-DAG: <<Not:i\d+>> Not [<<Get>>]
@@ -30,7 +41,7 @@
   /// CHECK-START-ARM64: int Main.operations() instruction_simplifier_arm64 (after)
   /// CHECK-DAG: <<Get:i\d+>> ArrayGet
   /// CHECK-DAG: <<Not:i\d+>> Not [<<Get>>]
-  /// CHECK-DAG:              Arm64DataProcWithShifterOp [<<Not>>,<<Get>>] kind:And+LSL shift:2
+  /// CHECK-DAG:              DataProcWithShifterOp [<<Not>>,<<Get>>] kind:And+LSL shift:2
   private static int operations() {
      int r = a[0];
      int n = ~r;
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 b35dff4..e1af02e 100644
--- a/test/626-const-class-linking/clear_dex_cache_types.cc
+++ b/test/626-const-class-linking/clear_dex_cache_types.cc
@@ -27,7 +27,8 @@
   ScopedObjectAccess soa(Thread::Current());
   mirror::DexCache* dex_cache = soa.Decode<mirror::Class>(cls)->GetDexCache();
   for (size_t i = 0, num_types = dex_cache->NumResolvedTypes(); i != num_types; ++i) {
-    dex_cache->SetResolvedType(dex::TypeIndex(i), ObjPtr<mirror::Class>(nullptr));
+    mirror::TypeDexCachePair cleared(nullptr, mirror::TypeDexCachePair::InvalidIndexForSlot(i));
+    dex_cache->GetResolvedTypes()[i].store(cleared, std::memory_order_relaxed);
   }
 }
 
diff --git a/test/638-no-line-number/build b/test/638-no-line-number/build
new file mode 100644
index 0000000..7eaf50e
--- /dev/null
+++ b/test/638-no-line-number/build
@@ -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.
+
+# Stop if something fails.
+set -e
+
+mkdir classes
+# Only keep the source name, to make sure we do remove it in the stack trace
+# when there is no line number mapping.
+${JAVAC} -g:source -source 7 -target 7 -d classes `find src -name '*.java'`
+${DX} --dex --output=classes.dex classes
+zip $TEST_NAME.jar classes.dex
diff --git a/test/638-no-line-number/expected.txt b/test/638-no-line-number/expected.txt
new file mode 100644
index 0000000..ffde153
--- /dev/null
+++ b/test/638-no-line-number/expected.txt
@@ -0,0 +1,5 @@
+java.lang.Error
+	at Main.main(Unknown Source:2)
+java.lang.NullPointerException: throw with null exception
+	at Main.doThrow(Unknown Source:0)
+	at Main.main(Unknown Source:9)
diff --git a/test/638-no-line-number/info.txt b/test/638-no-line-number/info.txt
new file mode 100644
index 0000000..89e6432
--- /dev/null
+++ b/test/638-no-line-number/info.txt
@@ -0,0 +1 @@
+Test for b/30183883, that we emit the dex pc when the line number is missing.
diff --git a/test/638-no-line-number/src/Main.java b/test/638-no-line-number/src/Main.java
new file mode 100644
index 0000000..7fe0404
--- /dev/null
+++ b/test/638-no-line-number/src/Main.java
@@ -0,0 +1,34 @@
+/*
+ * 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) {
+    try {
+      doThrow(new Error());
+    } catch (Error e) {
+      e.printStackTrace();
+    }
+    try {
+      doThrow(null);
+    } catch (Throwable t) {
+      t.printStackTrace();
+    }
+  }
+
+  public static void doThrow(Error e) {
+    throw e;
+  }
+}
diff --git a/test/906-iterate-heap/expected.txt b/test/906-iterate-heap/expected.txt
index 72cd47d..c8228d6 100644
--- a/test/906-iterate-heap/expected.txt
+++ b/test/906-iterate-heap/expected.txt
@@ -1,2 +1,20 @@
-[{tag=1, class-tag=0, size=8, length=-1}, {tag=2, class-tag=100, size=8, length=-1}, {tag=3, class-tag=100, size=8, length=-1}, {tag=4, class-tag=0, size=32, length=5}, {tag=100, class-tag=0, size=<class>, length=-1}]
-[{tag=11, class-tag=0, size=8, length=-1}, {tag=12, class-tag=110, size=8, length=-1}, {tag=13, class-tag=110, size=8, length=-1}, {tag=14, class-tag=0, size=32, length=5}, {tag=110, class-tag=0, size=<class>, length=-1}]
+[{tag=1, class-tag=0, size=8, length=-1}, {tag=2, class-tag=100, size=8, length=-1}, {tag=3, class-tag=100, size=8, length=-1}, {tag=4, class-tag=0, size=32, length=5}, {tag=5, class-tag=0, size=40, length=-1}, {tag=100, class-tag=0, size=<class>, length=-1}]
+[{tag=11, class-tag=0, size=8, length=-1}, {tag=12, class-tag=110, size=8, length=-1}, {tag=13, class-tag=110, size=8, length=-1}, {tag=14, class-tag=0, size=32, length=5}, {tag=15, class-tag=0, size=40, length=-1}, {tag=110, class-tag=0, size=<class>, length=-1}]
+15@0 (40, 'Hello World')
+16
+1@0 (14, 2xZ '0001')
+2
+1@0 (15, 3xB '010203')
+2
+1@0 (16, 2xC '41005a00')
+2
+1@0 (18, 3xS '010002000300')
+2
+1@0 (24, 3xI '010000000200000003000000')
+2
+1@0 (20, 2xF '000000000000803f')
+2
+1@0 (40, 3xJ '010000000000000002000000000000000300000000000000')
+2
+1@0 (32, 2xD '0000000000000000000000000000f03f')
+2
diff --git a/test/906-iterate-heap/iterate_heap.cc b/test/906-iterate-heap/iterate_heap.cc
index 1362d47..890220e 100644
--- a/test/906-iterate-heap/iterate_heap.cc
+++ b/test/906-iterate-heap/iterate_heap.cc
@@ -14,17 +14,23 @@
  * limitations under the License.
  */
 
+#include "inttypes.h"
+
+#include <iomanip>
 #include <iostream>
 #include <pthread.h>
+#include <sstream>
 #include <stdio.h>
 #include <vector>
 
+#include "android-base/stringprintf.h"
 #include "base/logging.h"
 #include "jni.h"
 #include "openjdkjvmti/jvmti.h"
 #include "ScopedPrimitiveArray.h"
 #include "ti-agent/common_helper.h"
 #include "ti-agent/common_load.h"
+#include "utf.h"
 
 namespace art {
 namespace Test906IterateHeap {
@@ -172,5 +178,149 @@
   Run(heap_filter, klass_filter, &config);
 }
 
+extern "C" JNIEXPORT jstring JNICALL Java_Main_iterateThroughHeapString(
+    JNIEnv* env, jclass klass ATTRIBUTE_UNUSED, jlong tag) {
+  struct FindStringCallbacks {
+    explicit FindStringCallbacks(jlong t) : tag_to_find(t) {}
+
+    static jint JNICALL HeapIterationCallback(jlong class_tag ATTRIBUTE_UNUSED,
+                                              jlong size ATTRIBUTE_UNUSED,
+                                              jlong* tag_ptr ATTRIBUTE_UNUSED,
+                                              jint length ATTRIBUTE_UNUSED,
+                                              void* user_data ATTRIBUTE_UNUSED) {
+      return 0;
+    }
+
+    static jint JNICALL StringValueCallback(jlong class_tag,
+                                            jlong size,
+                                            jlong* tag_ptr,
+                                            const jchar* value,
+                                            jint value_length,
+                                            void* user_data) {
+      FindStringCallbacks* p = reinterpret_cast<FindStringCallbacks*>(user_data);
+      if (*tag_ptr == p->tag_to_find) {
+        size_t utf_byte_count = CountUtf8Bytes(value, value_length);
+        std::unique_ptr<char[]> mod_utf(new char[utf_byte_count + 1]);
+        memset(mod_utf.get(), 0, utf_byte_count + 1);
+        ConvertUtf16ToModifiedUtf8(mod_utf.get(), utf_byte_count, value, value_length);
+        if (!p->data.empty()) {
+          p->data += "\n";
+        }
+        p->data += android::base::StringPrintf("%" PRId64 "@%" PRId64 " (%" PRId64 ", '%s')",
+                                               *tag_ptr,
+                                               class_tag,
+                                               size,
+                                               mod_utf.get());
+        // Update the tag to test whether that works.
+        *tag_ptr = *tag_ptr + 1;
+      }
+      return 0;
+    }
+
+    std::string data;
+    const jlong tag_to_find;
+  };
+
+  jvmtiHeapCallbacks callbacks;
+  memset(&callbacks, 0, sizeof(jvmtiHeapCallbacks));
+  callbacks.heap_iteration_callback = FindStringCallbacks::HeapIterationCallback;
+  callbacks.string_primitive_value_callback = FindStringCallbacks::StringValueCallback;
+
+  FindStringCallbacks fsc(tag);
+  jvmtiError ret = jvmti_env->IterateThroughHeap(0, nullptr, &callbacks, &fsc);
+  if (JvmtiErrorToException(env, ret)) {
+    return nullptr;
+  }
+  return env->NewStringUTF(fsc.data.c_str());
+}
+
+extern "C" JNIEXPORT jstring JNICALL Java_Main_iterateThroughHeapPrimitiveArray(
+    JNIEnv* env, jclass klass ATTRIBUTE_UNUSED, jlong tag) {
+  struct FindArrayCallbacks {
+    explicit FindArrayCallbacks(jlong t) : tag_to_find(t) {}
+
+    static jint JNICALL HeapIterationCallback(jlong class_tag ATTRIBUTE_UNUSED,
+                                              jlong size ATTRIBUTE_UNUSED,
+                                              jlong* tag_ptr ATTRIBUTE_UNUSED,
+                                              jint length ATTRIBUTE_UNUSED,
+                                              void* user_data ATTRIBUTE_UNUSED) {
+      return 0;
+    }
+
+    static jint JNICALL ArrayValueCallback(jlong class_tag,
+                                           jlong size,
+                                           jlong* tag_ptr,
+                                           jint element_count,
+                                           jvmtiPrimitiveType element_type,
+                                           const void* elements,
+                                           void* user_data) {
+      FindArrayCallbacks* p = reinterpret_cast<FindArrayCallbacks*>(user_data);
+      if (*tag_ptr == p->tag_to_find) {
+        std::ostringstream oss;
+        oss << *tag_ptr
+            << '@'
+            << class_tag
+            << " ("
+            << size
+            << ", "
+            << element_count
+            << "x"
+            << static_cast<char>(element_type)
+            << " '";
+        size_t element_size;
+        switch (element_type) {
+          case JVMTI_PRIMITIVE_TYPE_BOOLEAN:
+          case JVMTI_PRIMITIVE_TYPE_BYTE:
+            element_size = 1;
+            break;
+          case JVMTI_PRIMITIVE_TYPE_CHAR:
+          case JVMTI_PRIMITIVE_TYPE_SHORT:
+            element_size = 2;
+            break;
+          case JVMTI_PRIMITIVE_TYPE_INT:
+          case JVMTI_PRIMITIVE_TYPE_FLOAT:
+            element_size = 4;
+            break;
+          case JVMTI_PRIMITIVE_TYPE_LONG:
+          case JVMTI_PRIMITIVE_TYPE_DOUBLE:
+            element_size = 8;
+            break;
+          default:
+            LOG(FATAL) << "Unknown type " << static_cast<size_t>(element_type);
+            UNREACHABLE();
+        }
+        const uint8_t* data = reinterpret_cast<const uint8_t*>(elements);
+        for (size_t i = 0; i != element_size * element_count; ++i) {
+          oss << android::base::StringPrintf("%02x", data[i]);
+        }
+        oss << "')";
+
+        if (!p->data.empty()) {
+          p->data += "\n";
+        }
+        p->data += oss.str();
+        // Update the tag to test whether that works.
+        *tag_ptr = *tag_ptr + 1;
+      }
+      return 0;
+    }
+
+    std::string data;
+    const jlong tag_to_find;
+  };
+
+  jvmtiHeapCallbacks callbacks;
+  memset(&callbacks, 0, sizeof(jvmtiHeapCallbacks));
+  callbacks.heap_iteration_callback = FindArrayCallbacks::HeapIterationCallback;
+  callbacks.array_primitive_value_callback = FindArrayCallbacks::ArrayValueCallback;
+
+  FindArrayCallbacks fac(tag);
+  jvmtiError ret = jvmti_env->IterateThroughHeap(0, nullptr, &callbacks, &fac);
+  if (JvmtiErrorToException(env, ret)) {
+    return nullptr;
+  }
+  return env->NewStringUTF(fac.data.c_str());
+}
+
 }  // namespace Test906IterateHeap
 }  // namespace art
diff --git a/test/906-iterate-heap/src/Main.java b/test/906-iterate-heap/src/Main.java
index cab27be..d499886 100644
--- a/test/906-iterate-heap/src/Main.java
+++ b/test/906-iterate-heap/src/Main.java
@@ -28,11 +28,13 @@
     B b2 = new B();
     C c = new C();
     A[] aArray = new A[5];
+    String s = "Hello World";
 
     setTag(a, 1);
     setTag(b, 2);
     setTag(b2, 3);
     setTag(aArray, 4);
+    setTag(s, 5);
     setTag(B.class, 100);
 
     int all = iterateThroughHeapCount(0, null, Integer.MAX_VALUE);
@@ -50,7 +52,7 @@
       throw new IllegalStateException("By class: " + all + " != " + taggedClass + " + " +
           untaggedClass);
     }
-    if (tagged != 5) {
+    if (tagged != 6) {
       throw new IllegalStateException(tagged + " tagged objects");
     }
     if (taggedClass != 2) {
@@ -74,6 +76,49 @@
     iterateThroughHeapAdd(HEAP_FILTER_OUT_UNTAGGED, null);
     n = iterateThroughHeapData(HEAP_FILTER_OUT_UNTAGGED, null, classTags, sizes, tags, lengths);
     System.out.println(sort(n, classTags, sizes, tags, lengths));
+
+    System.out.println(iterateThroughHeapString(getTag(s)));
+    System.out.println(getTag(s));
+
+    boolean[] zArray = new boolean[] { false, true };
+    setTag(zArray, 1);
+    System.out.println(iterateThroughHeapPrimitiveArray(getTag(zArray)));
+    System.out.println(getTag(zArray));
+
+    byte[] bArray = new byte[] { 1, 2, 3 };
+    setTag(bArray, 1);
+    System.out.println(iterateThroughHeapPrimitiveArray(getTag(bArray)));
+    System.out.println(getTag(bArray));
+
+    char[] cArray = new char[] { 'A', 'Z' };
+    setTag(cArray, 1);
+    System.out.println(iterateThroughHeapPrimitiveArray(getTag(cArray)));
+    System.out.println(getTag(cArray));
+
+    short[] sArray = new short[] { 1, 2, 3 };
+    setTag(sArray, 1);
+    System.out.println(iterateThroughHeapPrimitiveArray(getTag(sArray)));
+    System.out.println(getTag(sArray));
+
+    int[] iArray = new int[] { 1, 2, 3 };
+    setTag(iArray, 1);
+    System.out.println(iterateThroughHeapPrimitiveArray(getTag(iArray)));
+    System.out.println(getTag(iArray));
+
+    float[] fArray = new float[] { 0.0f, 1.0f };
+    setTag(fArray, 1);
+    System.out.println(iterateThroughHeapPrimitiveArray(getTag(fArray)));
+    System.out.println(getTag(fArray));
+
+    long[] lArray = new long[] { 1, 2, 3 };
+    setTag(lArray, 1);
+    System.out.println(iterateThroughHeapPrimitiveArray(getTag(lArray)));
+    System.out.println(getTag(lArray));
+
+    double[] dArray = new double[] { 0.0, 1.0 };
+    setTag(dArray, 1);
+    System.out.println(iterateThroughHeapPrimitiveArray(getTag(dArray)));
+    System.out.println(getTag(dArray));
   }
 
   static class A {
@@ -141,4 +186,6 @@
       Class<?> klassFilter, long classTags[], long sizes[], long tags[], int lengths[]);
   private static native int iterateThroughHeapAdd(int heapFilter,
       Class<?> klassFilter);
+  private static native String iterateThroughHeapString(long tag);
+  private static native String iterateThroughHeapPrimitiveArray(long tag);
 }
diff --git a/test/909-attach-agent/attach.cc b/test/909-attach-agent/attach.cc
index 2b50eb8..adae844 100644
--- a/test/909-attach-agent/attach.cc
+++ b/test/909-attach-agent/attach.cc
@@ -28,7 +28,7 @@
 jint OnAttach(JavaVM* vm,
             char* options ATTRIBUTE_UNUSED,
             void* reserved ATTRIBUTE_UNUSED) {
-  printf("Attached Agent for test 909-attach-agent\n");
+  fprintf(stderr, "Attached Agent for test 909-attach-agent\n");
   fsync(1);
   jvmtiEnv* env = nullptr;
   jvmtiEnv* env2 = nullptr;
@@ -36,7 +36,7 @@
 #define CHECK_CALL_SUCCESS(c) \
   do { \
     if ((c) != JNI_OK) { \
-      printf("call " #c " did not succeed\n"); \
+      fprintf(stderr, "call " #c " did not succeed\n"); \
       return -1; \
     } \
   } while (false)
@@ -44,7 +44,7 @@
   CHECK_CALL_SUCCESS(vm->GetEnv(reinterpret_cast<void**>(&env), JVMTI_VERSION_1_0));
   CHECK_CALL_SUCCESS(vm->GetEnv(reinterpret_cast<void**>(&env2), JVMTI_VERSION_1_0));
   if (env == env2) {
-    printf("GetEnv returned same environment twice!\n");
+    fprintf(stderr, "GetEnv returned same environment twice!\n");
     return -1;
   }
   unsigned char* local_data = nullptr;
@@ -54,19 +54,19 @@
   unsigned char* get_data = nullptr;
   CHECK_CALL_SUCCESS(env->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&get_data)));
   if (get_data != local_data) {
-    printf("Got different data from local storage then what was set!\n");
+    fprintf(stderr, "Got different data from local storage then what was set!\n");
     return -1;
   }
   CHECK_CALL_SUCCESS(env2->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&get_data)));
   if (get_data != nullptr) {
-    printf("env2 did not have nullptr local storage.\n");
+    fprintf(stderr, "env2 did not have nullptr local storage.\n");
     return -1;
   }
   CHECK_CALL_SUCCESS(env->Deallocate(local_data));
   jint version = 0;
   CHECK_CALL_SUCCESS(env->GetVersionNumber(&version));
   if ((version & JVMTI_VERSION_1) != JVMTI_VERSION_1) {
-    printf("Unexpected version number!\n");
+    fprintf(stderr, "Unexpected version number!\n");
     return -1;
   }
   CHECK_CALL_SUCCESS(env->DisposeEnvironment());
diff --git a/test/912-classes/expected.txt b/test/912-classes/expected.txt
index e932b20..6b86ac9 100644
--- a/test/912-classes/expected.txt
+++ b/test/912-classes/expected.txt
@@ -15,7 +15,8 @@
 int interface=false array=false modifiable=false
 $Proxy0 interface=false array=false modifiable=false
 java.lang.Runnable interface=true array=false modifiable=false
-java.lang.String interface=false array=false modifiable=true
+java.lang.String interface=false array=false modifiable=false
+java.util.ArrayList interface=false array=false modifiable=true
 [I interface=false array=true modifiable=false
 [Ljava.lang.Runnable; interface=false array=true modifiable=false
 [Ljava.lang.String; interface=false array=true modifiable=false
diff --git a/test/912-classes/src/Main.java b/test/912-classes/src/Main.java
index 005074f..5d25d76 100644
--- a/test/912-classes/src/Main.java
+++ b/test/912-classes/src/Main.java
@@ -17,6 +17,7 @@
 import java.lang.ref.Reference;
 import java.lang.reflect.Constructor;
 import java.lang.reflect.Proxy;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Comparator;
 
@@ -40,6 +41,7 @@
     testClassType(getProxyClass());
     testClassType(Runnable.class);
     testClassType(String.class);
+    testClassType(ArrayList.class);
 
     testClassType(int[].class);
     testClassType(Runnable[].class);
diff --git a/test/913-heaps/expected.txt b/test/913-heaps/expected.txt
index 7522a65..6432172 100644
--- a/test/913-heaps/expected.txt
+++ b/test/913-heaps/expected.txt
@@ -1,6 +1,6 @@
 ---
 true true
-root@root --(stack-local[id=1,tag=3000,depth=2,method=doFollowReferencesTestNonRoot,vreg=11,location= 31])--> 1@1000 [size=16, length=-1]
+root@root --(stack-local[id=1,tag=3000,depth=2,method=doFollowReferencesTestNonRoot,vreg=13,location= 32])--> 1@1000 [size=16, length=-1]
 root@root --(stack-local[id=1,tag=3000,depth=3,method=doFollowReferencesTest,vreg=1,location= 28])--> 3000@0 [size=132, length=-1]
 root@root --(thread)--> 3000@0 [size=132, length=-1]
 0@0 --(array-element@0)--> 1@1000 [size=16, length=-1]
@@ -40,9 +40,9 @@
 ---
 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 --(stack-local[id=1,tag=3000,depth=1,method=doFollowReferencesTestImpl,vreg=10,location= 6])--> 1@1000 [size=16, length=-1]
-root@root --(stack-local[id=1,tag=3000,depth=1,method=doFollowReferencesTestImpl,vreg=5,location= 6])--> 1@1000 [size=16, length=-1]
-root@root --(stack-local[id=1,tag=3000,depth=2,method=doFollowReferencesTestRoot,vreg=3,location= 18])--> 1@1000 [size=16, length=-1]
+root@root --(stack-local[id=1,tag=3000,depth=1,method=doFollowReferencesTestImpl,vreg=13,location= 10])--> 1@1000 [size=16, length=-1]
+root@root --(stack-local[id=1,tag=3000,depth=1,method=doFollowReferencesTestImpl,vreg=5,location= 10])--> 1@1000 [size=16, length=-1]
+root@root --(stack-local[id=1,tag=3000,depth=2,method=doFollowReferencesTestRoot,vreg=4,location= 19])--> 1@1000 [size=16, length=-1]
 root@root --(thread)--> 1@1000 [size=16, length=-1]
 root@root --(thread)--> 3000@0 [size=132, length=-1]
 1001@0 --(superclass)--> 1000@0 [size=123, length=-1]
@@ -79,3 +79,14 @@
 5@1002 --(field@28)--> 1@1000 [size=16, length=-1]
 6@1000 --(class)--> 1000@0 [size=123, length=-1]
 ---
+[1@0 (40, 'HelloWorld')]
+2
+2@0 (15, 3xB '010203')
+3@0 (16, 2xC '41005a00')
+8@0 (32, 2xD '0000000000000000000000000000f03f')
+6@0 (20, 2xF '000000000000803f')
+5@0 (24, 3xI '010000000200000003000000')
+7@0 (40, 3xJ '010000000000000002000000000000000300000000000000')
+4@0 (18, 3xS '010002000300')
+1@0 (14, 2xZ '0001')
+23456789
diff --git a/test/913-heaps/heaps.cc b/test/913-heaps/heaps.cc
index 6759919..1de1a69 100644
--- a/test/913-heaps/heaps.cc
+++ b/test/913-heaps/heaps.cc
@@ -493,5 +493,158 @@
   return ret;
 }
 
+extern "C" JNIEXPORT jobjectArray JNICALL Java_Main_followReferencesString(
+    JNIEnv* env, jclass klass ATTRIBUTE_UNUSED, jobject initial_object) {
+  struct FindStringCallbacks {
+    static jint JNICALL FollowReferencesCallback(
+        jvmtiHeapReferenceKind reference_kind ATTRIBUTE_UNUSED,
+        const jvmtiHeapReferenceInfo* reference_info ATTRIBUTE_UNUSED,
+        jlong class_tag ATTRIBUTE_UNUSED,
+        jlong referrer_class_tag ATTRIBUTE_UNUSED,
+        jlong size ATTRIBUTE_UNUSED,
+        jlong* tag_ptr ATTRIBUTE_UNUSED,
+        jlong* referrer_tag_ptr ATTRIBUTE_UNUSED,
+        jint length ATTRIBUTE_UNUSED,
+        void* user_data ATTRIBUTE_UNUSED) {
+      return JVMTI_VISIT_OBJECTS;  // Continue visiting.
+    }
+
+    static jint JNICALL StringValueCallback(jlong class_tag,
+                                            jlong size,
+                                            jlong* tag_ptr,
+                                            const jchar* value,
+                                            jint value_length,
+                                            void* user_data) {
+      FindStringCallbacks* p = reinterpret_cast<FindStringCallbacks*>(user_data);
+      if (*tag_ptr != 0) {
+        size_t utf_byte_count = CountUtf8Bytes(value, value_length);
+        std::unique_ptr<char[]> mod_utf(new char[utf_byte_count + 1]);
+        memset(mod_utf.get(), 0, utf_byte_count + 1);
+        ConvertUtf16ToModifiedUtf8(mod_utf.get(), utf_byte_count, value, value_length);
+        p->data.push_back(android::base::StringPrintf("%" PRId64 "@%" PRId64 " (%" PRId64 ", '%s')",
+                                                      *tag_ptr,
+                                                      class_tag,
+                                                      size,
+                                                      mod_utf.get()));
+        // Update the tag to test whether that works.
+        *tag_ptr = *tag_ptr + 1;
+      }
+      return 0;
+    }
+
+    std::vector<std::string> data;
+  };
+
+  jvmtiHeapCallbacks callbacks;
+  memset(&callbacks, 0, sizeof(jvmtiHeapCallbacks));
+  callbacks.heap_reference_callback = FindStringCallbacks::FollowReferencesCallback;
+  callbacks.string_primitive_value_callback = FindStringCallbacks::StringValueCallback;
+
+  FindStringCallbacks fsc;
+  jvmtiError ret = jvmti_env->FollowReferences(0, nullptr, initial_object, &callbacks, &fsc);
+  if (JvmtiErrorToException(env, ret)) {
+    return nullptr;
+  }
+
+  jobjectArray retArray = CreateObjectArray(env,
+                                            static_cast<jint>(fsc.data.size()),
+                                            "java/lang/String",
+                                            [&](jint i) {
+                                              return env->NewStringUTF(fsc.data[i].c_str());
+                                            });
+  return retArray;
+}
+
+
+extern "C" JNIEXPORT jstring JNICALL Java_Main_followReferencesPrimitiveArray(
+    JNIEnv* env, jclass klass ATTRIBUTE_UNUSED, jobject initial_object) {
+  struct FindArrayCallbacks {
+    static jint JNICALL FollowReferencesCallback(
+        jvmtiHeapReferenceKind reference_kind ATTRIBUTE_UNUSED,
+        const jvmtiHeapReferenceInfo* reference_info ATTRIBUTE_UNUSED,
+        jlong class_tag ATTRIBUTE_UNUSED,
+        jlong referrer_class_tag ATTRIBUTE_UNUSED,
+        jlong size ATTRIBUTE_UNUSED,
+        jlong* tag_ptr ATTRIBUTE_UNUSED,
+        jlong* referrer_tag_ptr ATTRIBUTE_UNUSED,
+        jint length ATTRIBUTE_UNUSED,
+        void* user_data ATTRIBUTE_UNUSED) {
+      return JVMTI_VISIT_OBJECTS;  // Continue visiting.
+    }
+
+    static jint JNICALL ArrayValueCallback(jlong class_tag,
+                                           jlong size,
+                                           jlong* tag_ptr,
+                                           jint element_count,
+                                           jvmtiPrimitiveType element_type,
+                                           const void* elements,
+                                           void* user_data) {
+      FindArrayCallbacks* p = reinterpret_cast<FindArrayCallbacks*>(user_data);
+      if (*tag_ptr != 0) {
+        std::ostringstream oss;
+        oss << *tag_ptr
+            << '@'
+            << class_tag
+            << " ("
+            << size
+            << ", "
+            << element_count
+            << "x"
+            << static_cast<char>(element_type)
+            << " '";
+        size_t element_size;
+        switch (element_type) {
+          case JVMTI_PRIMITIVE_TYPE_BOOLEAN:
+          case JVMTI_PRIMITIVE_TYPE_BYTE:
+            element_size = 1;
+            break;
+          case JVMTI_PRIMITIVE_TYPE_CHAR:
+          case JVMTI_PRIMITIVE_TYPE_SHORT:
+            element_size = 2;
+            break;
+          case JVMTI_PRIMITIVE_TYPE_INT:
+          case JVMTI_PRIMITIVE_TYPE_FLOAT:
+            element_size = 4;
+            break;
+          case JVMTI_PRIMITIVE_TYPE_LONG:
+          case JVMTI_PRIMITIVE_TYPE_DOUBLE:
+            element_size = 8;
+            break;
+          default:
+            LOG(FATAL) << "Unknown type " << static_cast<size_t>(element_type);
+            UNREACHABLE();
+        }
+        const uint8_t* data = reinterpret_cast<const uint8_t*>(elements);
+        for (size_t i = 0; i != element_size * element_count; ++i) {
+          oss << android::base::StringPrintf("%02x", data[i]);
+        }
+        oss << "')";
+
+        if (!p->data.empty()) {
+          p->data += "\n";
+        }
+        p->data += oss.str();
+        // Update the tag to test whether that works.
+        *tag_ptr = *tag_ptr + 1;
+      }
+      return 0;
+    }
+
+    std::string data;
+  };
+
+  jvmtiHeapCallbacks callbacks;
+  memset(&callbacks, 0, sizeof(jvmtiHeapCallbacks));
+  callbacks.heap_reference_callback = FindArrayCallbacks::FollowReferencesCallback;
+  callbacks.array_primitive_value_callback = FindArrayCallbacks::ArrayValueCallback;
+
+  FindArrayCallbacks fac;
+  jvmtiError ret = jvmti_env->FollowReferences(0, nullptr, initial_object, &callbacks, &fac);
+  if (JvmtiErrorToException(env, ret)) {
+    return nullptr;
+  }
+  return env->NewStringUTF(fac.data.c_str());
+}
+
 }  // namespace Test913Heaps
 }  // namespace art
diff --git a/test/913-heaps/src/Main.java b/test/913-heaps/src/Main.java
index 5a11a5b..2767d89 100644
--- a/test/913-heaps/src/Main.java
+++ b/test/913-heaps/src/Main.java
@@ -15,6 +15,7 @@
  */
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -22,7 +23,10 @@
 public class Main {
   public static void main(String[] args) throws Exception {
     doTest();
-    doFollowReferencesTest();
+    new TestConfig().doFollowReferencesTest();
+
+    doStringTest();
+    doPrimitiveArrayTest();
   }
 
   public static void doTest() throws Exception {
@@ -33,6 +37,64 @@
     enableGcTracking(false);
   }
 
+  public static void doStringTest() throws Exception {
+    final String str = "HelloWorld";
+    Object o = new Object() {
+      String s = str;
+    };
+
+    setTag(str, 1);
+    System.out.println(Arrays.toString(followReferencesString(o)));
+    System.out.println(getTag(str));
+  }
+
+  public static void doPrimitiveArrayTest() throws Exception {
+    final boolean[] zArray = new boolean[] { false, true };
+    setTag(zArray, 1);
+
+    final byte[] bArray = new byte[] { 1, 2, 3 };
+    setTag(bArray, 2);
+
+    final char[] cArray = new char[] { 'A', 'Z' };
+    setTag(cArray, 3);
+
+    final short[] sArray = new short[] { 1, 2, 3 };
+    setTag(sArray, 4);
+
+    final int[] iArray = new int[] { 1, 2, 3 };
+    setTag(iArray, 5);
+
+    final float[] fArray = new float[] { 0.0f, 1.0f };
+    setTag(fArray, 6);
+
+    final long[] lArray = new long[] { 1, 2, 3 };
+    setTag(lArray, 7);
+
+    final double[] dArray = new double[] { 0.0, 1.0 };
+    setTag(dArray, 8);
+
+    Object o = new Object() {
+      Object z = zArray;
+      Object b = bArray;
+      Object c = cArray;
+      Object s = sArray;
+      Object i = iArray;
+      Object f = fArray;
+      Object l = lArray;
+      Object d = dArray;
+    };
+
+    System.out.println(followReferencesPrimitiveArray(o));
+    System.out.print(getTag(zArray));
+    System.out.print(getTag(bArray));
+    System.out.print(getTag(cArray));
+    System.out.print(getTag(sArray));
+    System.out.print(getTag(iArray));
+    System.out.print(getTag(fArray));
+    System.out.print(getTag(lArray));
+    System.out.println(getTag(dArray));
+  }
+
   private static void run() {
     clearStats();
     forceGarbageCollection();
@@ -51,126 +113,136 @@
     System.out.println((s > 0) + " " + (f > 0));
   }
 
-  public static void doFollowReferencesTest() throws Exception {
-    // Force GCs to clean up dirt.
-    Runtime.getRuntime().gc();
-    Runtime.getRuntime().gc();
+  private static class TestConfig {
+    private Class<?> klass = null;
+    private int heapFilter = 0;
 
-    setTag(Thread.currentThread(), 3000);
-
-    {
-      ArrayList<Object> tmpStorage = new ArrayList<>();
-      doFollowReferencesTestNonRoot(tmpStorage);
-      tmpStorage = null;
+    public TestConfig() {
+    }
+    public TestConfig(Class<?> klass, int heapFilter) {
+      this.klass = klass;
+      this.heapFilter = heapFilter;
     }
 
-    // Force GCs to clean up dirt.
-    Runtime.getRuntime().gc();
-    Runtime.getRuntime().gc();
+    public void doFollowReferencesTest() throws Exception {
+      // Force GCs to clean up dirt.
+      Runtime.getRuntime().gc();
+      Runtime.getRuntime().gc();
 
-    doFollowReferencesTestRoot();
+      setTag(Thread.currentThread(), 3000);
 
-    // Force GCs to clean up dirt.
-    Runtime.getRuntime().gc();
-    Runtime.getRuntime().gc();
-  }
+      {
+        ArrayList<Object> tmpStorage = new ArrayList<>();
+        doFollowReferencesTestNonRoot(tmpStorage);
+        tmpStorage = null;
+      }
 
-  private static void doFollowReferencesTestNonRoot(ArrayList<Object> tmpStorage) {
-    Verifier v = new Verifier();
-    tagClasses(v);
-    A a = createTree(v);
-    tmpStorage.add(a);
-    v.add("0@0", "1@1000");  // tmpStorage[0] --(array-element)--> a.
+      // Force GCs to clean up dirt.
+      Runtime.getRuntime().gc();
+      Runtime.getRuntime().gc();
 
-    doFollowReferencesTestImpl(null, Integer.MAX_VALUE, -1, null, v, null);
-    doFollowReferencesTestImpl(a.foo2, Integer.MAX_VALUE, -1, null, v, "3@1001");
+      doFollowReferencesTestRoot();
 
-    tmpStorage.clear();
-  }
+      // Force GCs to clean up dirt.
+      Runtime.getRuntime().gc();
+      Runtime.getRuntime().gc();
+    }
 
-  private static void doFollowReferencesTestRoot() {
-    Verifier v = new Verifier();
-    tagClasses(v);
-    A a = createTree(v);
+    private void doFollowReferencesTestNonRoot(ArrayList<Object> tmpStorage) {
+      Verifier v = new Verifier();
+      tagClasses(v);
+      A a = createTree(v);
+      tmpStorage.add(a);
+      v.add("0@0", "1@1000");  // tmpStorage[0] --(array-element)--> a.
 
-    doFollowReferencesTestImpl(null, Integer.MAX_VALUE, -1, a, v, null);
-    doFollowReferencesTestImpl(a.foo2, Integer.MAX_VALUE, -1, a, v, "3@1001");
-  }
+      doFollowReferencesTestImpl(null, Integer.MAX_VALUE, -1, null, v, null);
+      doFollowReferencesTestImpl(a.foo2, Integer.MAX_VALUE, -1, null, v, "3@1001");
 
-  private static void doFollowReferencesTestImpl(A root, int stopAfter, int followSet,
-      Object asRoot, Verifier v, String additionalEnabled) {
-    String[] lines =
-        followReferences(0, null, root, stopAfter, followSet, asRoot);
+      tmpStorage.clear();
+    }
 
-    v.process(lines, additionalEnabled);
+    private void doFollowReferencesTestRoot() {
+      Verifier v = new Verifier();
+      tagClasses(v);
+      A a = createTree(v);
 
-    // TODO: Test filters.
-  }
+      doFollowReferencesTestImpl(null, Integer.MAX_VALUE, -1, a, v, null);
+      doFollowReferencesTestImpl(a.foo2, Integer.MAX_VALUE, -1, a, v, "3@1001");
+    }
 
-  private static void tagClasses(Verifier v) {
-    setTag(A.class, 1000);
+    private void doFollowReferencesTestImpl(A root, int stopAfter, int followSet,
+        Object asRoot, Verifier v, String additionalEnabled) {
+      String[] lines =
+          followReferences(heapFilter, klass, root, stopAfter, followSet, asRoot);
 
-    setTag(B.class, 1001);
-    v.add("1001@0", "1000@0");  // B.class --(superclass)--> A.class.
+      v.process(lines, additionalEnabled, heapFilter != 0 || klass != null);
+    }
 
-    setTag(C.class, 1002);
-    v.add("1002@0", "1001@0");  // C.class --(superclass)--> B.class.
-    v.add("1002@0", "2001@0");  // C.class --(interface)--> I2.class.
+    private static void tagClasses(Verifier v) {
+      setTag(A.class, 1000);
 
-    setTag(I1.class, 2000);
+      setTag(B.class, 1001);
+      v.add("1001@0", "1000@0");  // B.class --(superclass)--> A.class.
 
-    setTag(I2.class, 2001);
-    v.add("2001@0", "2000@0");  // I2.class --(interface)--> I1.class.
-  }
+      setTag(C.class, 1002);
+      v.add("1002@0", "1001@0");  // C.class --(superclass)--> B.class.
+      v.add("1002@0", "2001@0");  // C.class --(interface)--> I2.class.
 
-  private static A createTree(Verifier v) {
-    A aInst = new A();
-    setTag(aInst, 1);
-    String aInstStr = "1@1000";
-    String aClassStr = "1000@0";
-    v.add(aInstStr, aClassStr);  // A -->(class) --> A.class.
+      setTag(I1.class, 2000);
 
-    A a2Inst = new A();
-    setTag(a2Inst, 2);
-    aInst.foo = a2Inst;
-    String a2InstStr = "2@1000";
-    v.add(a2InstStr, aClassStr);  // A2 -->(class) --> A.class.
-    v.add(aInstStr, a2InstStr);   // A -->(field) --> A2.
+      setTag(I2.class, 2001);
+      v.add("2001@0", "2000@0");  // I2.class --(interface)--> I1.class.
+    }
 
-    B bInst = new B();
-    setTag(bInst, 3);
-    aInst.foo2 = bInst;
-    String bInstStr = "3@1001";
-    String bClassStr = "1001@0";
-    v.add(bInstStr, bClassStr);  // B -->(class) --> B.class.
-    v.add(aInstStr, bInstStr);   // A -->(field) --> B.
+    private static A createTree(Verifier v) {
+      A aInst = new A();
+      setTag(aInst, 1);
+      String aInstStr = "1@1000";
+      String aClassStr = "1000@0";
+      v.add(aInstStr, aClassStr);  // A -->(class) --> A.class.
 
-    A a3Inst = new A();
-    setTag(a3Inst, 4);
-    bInst.bar = a3Inst;
-    String a3InstStr = "4@1000";
-    v.add(a3InstStr, aClassStr);  // A3 -->(class) --> A.class.
-    v.add(bInstStr, a3InstStr);   // B -->(field) --> A3.
+      A a2Inst = new A();
+      setTag(a2Inst, 2);
+      aInst.foo = a2Inst;
+      String a2InstStr = "2@1000";
+      v.add(a2InstStr, aClassStr);  // A2 -->(class) --> A.class.
+      v.add(aInstStr, a2InstStr);   // A -->(field) --> A2.
 
-    C cInst = new C();
-    setTag(cInst, 5);
-    bInst.bar2 = cInst;
-    String cInstStr = "5@1000";
-    String cClassStr = "1002@0";
-    v.add(cInstStr, cClassStr);  // C -->(class) --> C.class.
-    v.add(bInstStr, cInstStr);   // B -->(field) --> C.
+      B bInst = new B();
+      setTag(bInst, 3);
+      aInst.foo2 = bInst;
+      String bInstStr = "3@1001";
+      String bClassStr = "1001@0";
+      v.add(bInstStr, bClassStr);  // B -->(class) --> B.class.
+      v.add(aInstStr, bInstStr);   // A -->(field) --> B.
 
-    A a4Inst = new A();
-    setTag(a4Inst, 6);
-    cInst.baz = a4Inst;
-    String a4InstStr = "6@1000";
-    v.add(a4InstStr, aClassStr);  // A4 -->(class) --> A.class.
-    v.add(cInstStr, a4InstStr);   // C -->(field) --> A4.
+      A a3Inst = new A();
+      setTag(a3Inst, 4);
+      bInst.bar = a3Inst;
+      String a3InstStr = "4@1000";
+      v.add(a3InstStr, aClassStr);  // A3 -->(class) --> A.class.
+      v.add(bInstStr, a3InstStr);   // B -->(field) --> A3.
 
-    cInst.baz2 = aInst;
-    v.add(cInstStr, aInstStr);  // C -->(field) --> A.
+      C cInst = new C();
+      setTag(cInst, 5);
+      bInst.bar2 = cInst;
+      String cInstStr = "5@1000";
+      String cClassStr = "1002@0";
+      v.add(cInstStr, cClassStr);  // C -->(class) --> C.class.
+      v.add(bInstStr, cInstStr);   // B -->(field) --> C.
 
-    return aInst;
+      A a4Inst = new A();
+      setTag(a4Inst, 6);
+      cInst.baz = a4Inst;
+      String a4InstStr = "6@1000";
+      v.add(a4InstStr, aClassStr);  // A4 -->(class) --> A.class.
+      v.add(cInstStr, a4InstStr);   // C -->(field) --> A4.
+
+      cInst.baz2 = aInst;
+      v.add(cInstStr, aInstStr);  // C -->(field) --> A.
+
+      return aInst;
+    }
   }
 
   public static class A {
@@ -243,7 +315,7 @@
       }
     }
 
-    public void process(String[] lines, String additionalEnabledReferrer) {
+    public void process(String[] lines, String additionalEnabledReferrer, boolean filtered) {
       // This method isn't optimal. The loops could be merged. However, it's more readable if
       // the different parts are separated.
 
@@ -303,6 +375,21 @@
         }
       }
 
+      if (filtered) {
+        // If we aren't tracking dependencies, just sort the lines and print.
+        // TODO: As the verifier is currently using the output lines to track dependencies, we
+        //       cannot verify that output is correct when parts of it are suppressed by filters.
+        //       To correctly track this we need to take node information into account, and
+        //       actually analyze the graph.
+        Collections.sort(nonRootLines);
+        for (String l : nonRootLines) {
+          System.out.println(l);
+        }
+
+        System.out.println("---");
+        return;
+      }
+
       // Iterate through the lines, keeping track of which referrers are visited, to ensure the
       // order is acceptable.
       HashSet<String> enabled = new HashSet<>();
@@ -379,9 +466,11 @@
   private static native int getGcFinishes();
   private static native void forceGarbageCollection();
 
-  private static native void setTag(Object o, long tag);
-  private static native long getTag(Object o);
+  public static native void setTag(Object o, long tag);
+  public static native long getTag(Object o);
 
-  private static native String[] followReferences(int heapFilter, Class<?> klassFilter,
+  public static native String[] followReferences(int heapFilter, Class<?> klassFilter,
       Object initialObject, int stopAfter, int followSet, Object jniRef);
+  public static native String[] followReferencesString(Object initialObject);
+  public static native String followReferencesPrimitiveArray(Object initialObject);
 }
diff --git a/test/946-obsolete-throw/src/Main.java b/test/946-obsolete-throw/src/Main.java
index 3ff97ae..21fe972 100644
--- a/test/946-obsolete-throw/src/Main.java
+++ b/test/946-obsolete-throw/src/Main.java
@@ -71,7 +71,7 @@
       t.sayHi(new DoRedefinitionClass());
     } catch (Throwable e) {
       System.out.println("Received error : " + e);
-      e.printStackTrace();
+      e.printStackTrace(System.out);
     }
     t.sayHi(() -> { System.out.println("Not doing anything here"); });
   }
diff --git a/test/950-redefine-intrinsic/expected.txt b/test/950-redefine-intrinsic/expected.txt
new file mode 100644
index 0000000..1264c94
--- /dev/null
+++ b/test/950-redefine-intrinsic/expected.txt
@@ -0,0 +1 @@
+Finished!
diff --git a/test/950-redefine-intrinsic/info.txt b/test/950-redefine-intrinsic/info.txt
new file mode 100644
index 0000000..c19d2b4
--- /dev/null
+++ b/test/950-redefine-intrinsic/info.txt
@@ -0,0 +1,3 @@
+Tests basic functions in the jvmti plugin.
+
+Tests that we are able to redefine intrinsic functions.
diff --git a/test/950-redefine-intrinsic/run b/test/950-redefine-intrinsic/run
new file mode 100755
index 0000000..e92b873
--- /dev/null
+++ b/test/950-redefine-intrinsic/run
@@ -0,0 +1,17 @@
+#!/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/src/Main.java b/test/950-redefine-intrinsic/src/Main.java
new file mode 100644
index 0000000..30cd3ab
--- /dev/null
+++ b/test/950-redefine-intrinsic/src/Main.java
@@ -0,0 +1,471 @@
+/*
+ * 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 java.util.Base64;
+import java.util.Random;
+import java.util.function.*;
+import java.util.stream.*;
+
+public class Main {
+
+  // The bytes below define the following java program.
+  // package java.lang;
+  // import java.math.*;
+  // public final class Long extends Number implements Comparable<Long> {
+  //     public static final long MIN_VALUE = 0;
+  //     public static final long MAX_VALUE = 0;
+  //     public static final Class<Long> TYPE = null;
+  //     static { }
+  //     // Used for Stream.count for some reason.
+  //     public static long sum(long a, long b) {
+  //       return a + b;
+  //     }
+  //     // Used in stream/lambda functions.
+  //     public Long(long value) {
+  //       this.value = value;
+  //     }
+  //     // Used in stream/lambda functions.
+  //     public static Long valueOf(long l) {
+  //       return new Long(l);
+  //     }
+  //     // Intrinsic! Do something cool. Return i + 1
+  //     public static long highestOneBit(long i) {
+  //       return i + 1;
+  //     }
+  //     // Intrinsic! Do something cool. Return i - 1
+  //     public static long lowestOneBit(long i) {
+  //       return i - 1;
+  //     }
+  //     // Intrinsic! Do something cool. Return i + i
+  //     public static int numberOfLeadingZeros(long i) {
+  //       return (int)(i + i);
+  //     }
+  //     // Intrinsic! Do something cool. Return i & (i >>> 1);
+  //     public static int numberOfTrailingZeros(long i) {
+  //       return (int)(i & (i >>> 1));
+  //     }
+  //     // Intrinsic! Do something cool. Return 5
+  //      public static int bitCount(long i) {
+  //        return 5;
+  //      }
+  //     // Intrinsic! Do something cool. Return i
+  //     public static long rotateLeft(long i, int distance) {
+  //       return i;
+  //     }
+  //     // Intrinsic! Do something cool. Return 10 * i
+  //     public static long rotateRight(long i, int distance) {
+  //       return 10 * i;
+  //     }
+  //     // Intrinsic! Do something cool. Return -i
+  //     public static long reverse(long i) {
+  //       return -i;
+  //     }
+  //     // Intrinsic! Do something cool. Return 0
+  //     public static int signum(long i) {
+  //       return 0;
+  //     }
+  //     // Intrinsic! Do something cool. Return 0
+  //     public static long reverseBytes(long i) {
+  //       return 0;
+  //     }
+  //     public String toString() {
+  //       return "Redefined Long! value (as double)=" + ((double)value);
+  //     }
+  //     public static String toString(long i, int radix) {
+  //       throw new Error("Method redefined away!");
+  //     }
+  //     public static String toUnsignedString(long i, int radix) {
+  //       throw new Error("Method redefined away!");
+  //     }
+  //     private static BigInteger toUnsignedBigInteger(long i) {
+  //       throw new Error("Method redefined away!");
+  //     }
+  //     public static String toHexString(long i) {
+  //       throw new Error("Method redefined away!");
+  //     }
+  //     public static String toOctalString(long i) {
+  //       throw new Error("Method redefined away!");
+  //     }
+  //     public static String toBinaryString(long i) {
+  //       throw new Error("Method redefined away!");
+  //     }
+  //     static String toUnsignedString0(long val, int shift) {
+  //       throw new Error("Method redefined away!");
+  //     }
+  //     static int formatUnsignedLong(long val, int shift, char[] buf, int offset, int len) {
+  //       throw new Error("Method redefined away!");
+  //     }
+  //     public static String toString(long i) {
+  //       throw new Error("Method redefined away!");
+  //     }
+  //     public static String toUnsignedString(long i) {
+  //       throw new Error("Method redefined away!");
+  //     }
+  //     static void getChars(long i, int index, char[] buf) {
+  //       throw new Error("Method redefined away!");
+  //     }
+  //     static int stringSize(long x) {
+  //       throw new Error("Method redefined away!");
+  //     }
+  //     public static long parseLong(String s, int radix) throws NumberFormatException {
+  //       throw new Error("Method redefined away!");
+  //     }
+  //     public static long parseLong(String s) throws NumberFormatException {
+  //       throw new Error("Method redefined away!");
+  //     }
+  //     public static long parseUnsignedLong(String s, int radix) throws NumberFormatException {
+  //       throw new Error("Method redefined away!");
+  //     }
+  //     public static long parseUnsignedLong(String s) throws NumberFormatException {
+  //       throw new Error("Method redefined away!");
+  //     }
+  //     public static Long valueOf(String s, int radix) throws NumberFormatException {
+  //       throw new Error("Method redefined away!");
+  //     }
+  //     public static Long valueOf(String s) throws NumberFormatException {
+  //       throw new Error("Method redefined away!");
+  //     }
+  //     public static Long decode(String nm) throws NumberFormatException {
+  //       throw new Error("Method redefined away!");
+  //     }
+  //     private final long value;
+  //     public Long(String s) throws NumberFormatException {
+  //       this(0);
+  //       throw new Error("Method redefined away!");
+  //     }
+  //     public byte byteValue() {
+  //       throw new Error("Method redefined away!");
+  //     }
+  //     public short shortValue() {
+  //       throw new Error("Method redefined away!");
+  //     }
+  //     public int intValue() {
+  //       throw new Error("Method redefined away!");
+  //     }
+  //     public long longValue() {
+  //       return value;
+  //     }
+  //     public float floatValue() {
+  //       throw new Error("Method redefined away!");
+  //     }
+  //     public double doubleValue() {
+  //       throw new Error("Method redefined away!");
+  //     }
+  //     public int hashCode() {
+  //       throw new Error("Method redefined away!");
+  //     }
+  //     public static int hashCode(long value) {
+  //       throw new Error("Method redefined away!");
+  //     }
+  //     public boolean equals(Object obj) {
+  //       throw new Error("Method redefined away!");
+  //     }
+  //     public static Long getLong(String nm) {
+  //       throw new Error("Method redefined away!");
+  //     }
+  //     public static Long getLong(String nm, long val) {
+  //       throw new Error("Method redefined away!");
+  //     }
+  //     public static Long getLong(String nm, Long val) {
+  //       throw new Error("Method redefined away!");
+  //     }
+  //     public int compareTo(Long anotherLong) {
+  //       throw new Error("Method redefined away!");
+  //     }
+  //     public static int compare(long x, long y) {
+  //       throw new Error("Method redefined away!");
+  //     }
+  //     public static int compareUnsigned(long x, long y) {
+  //       throw new Error("Method redefined away!");
+  //     }
+  //     public static long divideUnsigned(long dividend, long divisor) {
+  //       throw new Error("Method redefined away!");
+  //     }
+  //     public static long remainderUnsigned(long dividend, long divisor) {
+  //       throw new Error("Method redefined away!");
+  //     }
+  //     public static final int SIZE = 64;
+  //     public static final int BYTES = SIZE / Byte.SIZE;
+  //     public static long max(long a, long b) {
+  //       throw new Error("Method redefined away!");
+  //     }
+  //     public static long min(long a, long b) {
+  //       throw new Error("Method redefined away!");
+  //     }
+  //     private static final long serialVersionUID = 0;
+  // }
+  private static final byte[] CLASS_BYTES = Base64.getDecoder().decode(
+    "yv66vgAAADQAiQUAAAAAAAAACgcAdQoAAwB2CAB3CgADAHgJAA0AeQoAAwB6CgADAHsHAHwIAH0K" +
+    "AAoAfgcAfwoADQCACgASAHYKAA0AgQkADQCCBwCDBwCEAQAJTUlOX1ZBTFVFAQABSgEADUNvbnN0" +
+    "YW50VmFsdWUFAAAAAAAAAAABAAlNQVhfVkFMVUUBAARUWVBFAQARTGphdmEvbGFuZy9DbGFzczsB" +
+    "AAlTaWduYXR1cmUBACNMamF2YS9sYW5nL0NsYXNzPExqYXZhL2xhbmcvTG9uZzs+OwEABXZhbHVl" +
+    "AQAEU0laRQEAAUkDAAAAQAEABUJZVEVTAwAAAAgBABBzZXJpYWxWZXJzaW9uVUlEAQANaGlnaGVz" +
+    "dE9uZUJpdAEABChKKUoBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQAMbG93ZXN0T25lQml0AQAU" +
+    "bnVtYmVyT2ZMZWFkaW5nWmVyb3MBAAQoSilJAQAVbnVtYmVyT2ZUcmFpbGluZ1plcm9zAQAIYml0" +
+    "Q291bnQBAApyb3RhdGVMZWZ0AQAFKEpJKUoBAAtyb3RhdGVSaWdodAEAB3JldmVyc2UBAAZzaWdu" +
+    "dW0BAAxyZXZlcnNlQnl0ZXMBAAh0b1N0cmluZwEAFCgpTGphdmEvbGFuZy9TdHJpbmc7AQAWKEpJ" +
+    "KUxqYXZhL2xhbmcvU3RyaW5nOwEAEHRvVW5zaWduZWRTdHJpbmcBABR0b1Vuc2lnbmVkQmlnSW50" +
+    "ZWdlcgEAGShKKUxqYXZhL21hdGgvQmlnSW50ZWdlcjsBAAt0b0hleFN0cmluZwEAFShKKUxqYXZh" +
+    "L2xhbmcvU3RyaW5nOwEADXRvT2N0YWxTdHJpbmcBAA50b0JpbmFyeVN0cmluZwEAEXRvVW5zaWdu" +
+    "ZWRTdHJpbmcwAQASZm9ybWF0VW5zaWduZWRMb25nAQAJKEpJW0NJSSlJAQAIZ2V0Q2hhcnMBAAco" +
+    "SklbQylWAQAKc3RyaW5nU2l6ZQEACXBhcnNlTG9uZwEAFihMamF2YS9sYW5nL1N0cmluZztJKUoB" +
+    "AApFeGNlcHRpb25zBwCFAQAVKExqYXZhL2xhbmcvU3RyaW5nOylKAQARcGFyc2VVbnNpZ25lZExv" +
+    "bmcBAAd2YWx1ZU9mAQAlKExqYXZhL2xhbmcvU3RyaW5nO0kpTGphdmEvbGFuZy9Mb25nOwEAJChM" +
+    "amF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Mb25nOwEAEyhKKUxqYXZhL2xhbmcvTG9uZzsB" +
+    "AAZkZWNvZGUBAAY8aW5pdD4BAAQoSilWAQAVKExqYXZhL2xhbmcvU3RyaW5nOylWAQAJYnl0ZVZh" +
+    "bHVlAQADKClCAQAKc2hvcnRWYWx1ZQEAAygpUwEACGludFZhbHVlAQADKClJAQAJbG9uZ1ZhbHVl" +
+    "AQADKClKAQAKZmxvYXRWYWx1ZQEAAygpRgEAC2RvdWJsZVZhbHVlAQADKClEAQAIaGFzaENvZGUB" +
+    "AAZlcXVhbHMBABUoTGphdmEvbGFuZy9PYmplY3Q7KVoBAAdnZXRMb25nAQAlKExqYXZhL2xhbmcv" +
+    "U3RyaW5nO0opTGphdmEvbGFuZy9Mb25nOwEANChMamF2YS9sYW5nL1N0cmluZztMamF2YS9sYW5n" +
+    "L0xvbmc7KUxqYXZhL2xhbmcvTG9uZzsBAAljb21wYXJlVG8BABMoTGphdmEvbGFuZy9Mb25nOylJ" +
+    "AQAHY29tcGFyZQEABShKSilJAQAPY29tcGFyZVVuc2lnbmVkAQAOZGl2aWRlVW5zaWduZWQBAAUo" +
+    "SkopSgEAEXJlbWFpbmRlclVuc2lnbmVkAQADc3VtAQADbWF4AQADbWluAQAVKExqYXZhL2xhbmcv" +
+    "T2JqZWN0OylJAQAIPGNsaW5pdD4BAAMoKVYBADpMamF2YS9sYW5nL051bWJlcjtMamF2YS9sYW5n" +
+    "L0NvbXBhcmFibGU8TGphdmEvbGFuZy9Mb25nOz47AQAKU291cmNlRmlsZQEACUxvbmcuamF2YQEA" +
+    "F2phdmEvbGFuZy9TdHJpbmdCdWlsZGVyDABPAHEBACJSZWRlZmluZWQgTG9uZyEgdmFsdWUgKGFz" +
+    "IGRvdWJsZSk9DACGAIcMAB4AFQwAhgCIDAA0ADUBAA9qYXZhL2xhbmcvRXJyb3IBABZNZXRob2Qg" +
+    "cmVkZWZpbmVkIGF3YXkhDABPAFEBAA5qYXZhL2xhbmcvTG9uZwwATwBQDABkAGUMABoAGwEAEGph" +
+    "dmEvbGFuZy9OdW1iZXIBABRqYXZhL2xhbmcvQ29tcGFyYWJsZQEAH2phdmEvbGFuZy9OdW1iZXJG" +
+    "b3JtYXRFeGNlcHRpb24BAAZhcHBlbmQBAC0oTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcv" +
+    "U3RyaW5nQnVpbGRlcjsBABwoRClMamF2YS9sYW5nL1N0cmluZ0J1aWxkZXI7ADEADQASAAEAEwAH" +
+    "ABkAFAAVAAEAFgAAAAIAFwAZABkAFQABABYAAAACABcAGQAaABsAAQAcAAAAAgAdABIAHgAVAAAA" +
+    "GQAfACAAAQAWAAAAAgAhABkAIgAgAAEAFgAAAAIAIwAaACQAFQABABYAAAACABcANwAJACUAJgAB" +
+    "ACcAAAAcAAQAAgAAAAQeCmGtAAAAAQAoAAAABgABAAAADgAJACkAJgABACcAAAAcAAQAAgAAAAQe" +
+    "CmWtAAAAAQAoAAAABgABAAAAEwAJACoAKwABACcAAAAdAAQAAgAAAAUeHmGIrAAAAAEAKAAAAAYA" +
+    "AQAAABgACQAsACsAAQAnAAAAHwAFAAIAAAAHHh4EfX+IrAAAAAEAKAAAAAYAAQAAAB0ACQAtACsA" +
+    "AQAnAAAAGgABAAIAAAACCKwAAAABACgAAAAGAAEAAAAiAAkALgAvAAEAJwAAABoAAgADAAAAAh6t" +
+    "AAAAAQAoAAAABgABAAAAJwAJADAALwABACcAAAAeAAQAAwAAAAYUAAEeaa0AAAABACgAAAAGAAEA" +
+    "AAAsAAkAMQAmAAEAJwAAABsAAgACAAAAAx51rQAAAAEAKAAAAAYAAQAAADEACQAyACsAAQAnAAAA" +
+    "GgABAAIAAAACA6wAAAABACgAAAAGAAEAAAA2AAkAMwAmAAEAJwAAABoAAgACAAAAAgmtAAAAAQAo" +
+    "AAAABgABAAAAOwABADQANQABACcAAAAwAAMAAQAAABi7AANZtwAEEgW2AAYqtAAHirYACLYACbAA" +
+    "AAABACgAAAAGAAEAAAA/AAkANAA2AAEAJwAAACIAAwADAAAACrsAClkSC7cADL8AAAABACgAAAAG" +
+    "AAEAAABDAAkANwA2AAEAJwAAACIAAwADAAAACrsAClkSC7cADL8AAAABACgAAAAGAAEAAABGAAoA" +
+    "OAA5AAEAJwAAACIAAwACAAAACrsAClkSC7cADL8AAAABACgAAAAGAAEAAABKAAkAOgA7AAEAJwAA" +
+    "ACIAAwACAAAACrsAClkSC7cADL8AAAABACgAAAAGAAEAAABNAAkAPAA7AAEAJwAAACIAAwACAAAA" +
+    "CrsAClkSC7cADL8AAAABACgAAAAGAAEAAABRAAkAPQA7AAEAJwAAACIAAwACAAAACrsAClkSC7cA" +
+    "DL8AAAABACgAAAAGAAEAAABVAAgAPgA2AAEAJwAAACIAAwADAAAACrsAClkSC7cADL8AAAABACgA" +
+    "AAAGAAEAAABZAAgAPwBAAAEAJwAAACIAAwAGAAAACrsAClkSC7cADL8AAAABACgAAAAGAAEAAABd" +
+    "AAkANAA7AAEAJwAAACIAAwACAAAACrsAClkSC7cADL8AAAABACgAAAAGAAEAAABhAAkANwA7AAEA" +
+    "JwAAACIAAwACAAAACrsAClkSC7cADL8AAAABACgAAAAGAAEAAABlAAgAQQBCAAEAJwAAACIAAwAE" +
+    "AAAACrsAClkSC7cADL8AAAABACgAAAAGAAEAAABpAAgAQwArAAEAJwAAACIAAwACAAAACrsAClkS" +
+    "C7cADL8AAAABACgAAAAGAAEAAABtAAkARABFAAIAJwAAACIAAwACAAAACrsAClkSC7cADL8AAAAB" +
+    "ACgAAAAGAAEAAABxAEYAAAAEAAEARwAJAEQASAACACcAAAAiAAMAAQAAAAq7AApZEgu3AAy/AAAA" +
+    "AQAoAAAABgABAAAAdQBGAAAABAABAEcACQBJAEUAAgAnAAAAIgADAAIAAAAKuwAKWRILtwAMvwAA" +
+    "AAEAKAAAAAYAAQAAAHkARgAAAAQAAQBHAAkASQBIAAIAJwAAACIAAwABAAAACrsAClkSC7cADL8A" +
+    "AAABACgAAAAGAAEAAAB9AEYAAAAEAAEARwAJAEoASwACACcAAAAiAAMAAgAAAAq7AApZEgu3AAy/" +
+    "AAAAAQAoAAAABgABAAAAgQBGAAAABAABAEcACQBKAEwAAgAnAAAAIgADAAEAAAAKuwAKWRILtwAM" +
+    "vwAAAAEAKAAAAAYAAQAAAIQARgAAAAQAAQBHAAkASgBNAAEAJwAAACEABAACAAAACbsADVketwAO" +
+    "sAAAAAEAKAAAAAYAAQAAAIcACQBOAEwAAgAnAAAAIgADAAEAAAAKuwAKWRILtwAMvwAAAAEAKAAA" +
+    "AAYAAQAAAIsARgAAAAQAAQBHAAEATwBQAAEAJwAAACoAAwADAAAACiq3AA8qH7UAB7EAAAABACgA" +
+    "AAAOAAMAAACQAAQAkQAJAJIAAQBPAFEAAgAnAAAAKwADAAIAAAAPKgm3AA67AApZEgu3AAy/AAAA" +
+    "AQAoAAAACgACAAAAlQAFAJYARgAAAAQAAQBHAAEAUgBTAAEAJwAAACIAAwABAAAACrsAClkSC7cA" +
+    "DL8AAAABACgAAAAGAAEAAACaAAEAVABVAAEAJwAAACIAAwABAAAACrsAClkSC7cADL8AAAABACgA" +
+    "AAAGAAEAAACeAAEAVgBXAAEAJwAAACIAAwABAAAACrsAClkSC7cADL8AAAABACgAAAAGAAEAAACi" +
+    "AAEAWABZAAEAJwAAAB0AAgABAAAABSq0AAetAAAAAQAoAAAABgABAAAApgABAFoAWwABACcAAAAi" +
+    "AAMAAQAAAAq7AApZEgu3AAy/AAAAAQAoAAAABgABAAAAqgABAFwAXQABACcAAAAiAAMAAQAAAAq7" +
+    "AApZEgu3AAy/AAAAAQAoAAAABgABAAAArgABAF4AVwABACcAAAAiAAMAAQAAAAq7AApZEgu3AAy/" +
+    "AAAAAQAoAAAABgABAAAAsgAJAF4AKwABACcAAAAiAAMAAgAAAAq7AApZEgu3AAy/AAAAAQAoAAAA" +
+    "BgABAAAAtgABAF8AYAABACcAAAAiAAMAAgAAAAq7AApZEgu3AAy/AAAAAQAoAAAABgABAAAAugAJ" +
+    "AGEATAABACcAAAAiAAMAAQAAAAq7AApZEgu3AAy/AAAAAQAoAAAABgABAAAAvgAJAGEAYgABACcA" +
+    "AAAiAAMAAwAAAAq7AApZEgu3AAy/AAAAAQAoAAAABgABAAAAwgAJAGEAYwABACcAAAAiAAMAAgAA" +
+    "AAq7AApZEgu3AAy/AAAAAQAoAAAABgABAAAAxgABAGQAZQABACcAAAAiAAMAAgAAAAq7AApZEgu3" +
+    "AAy/AAAAAQAoAAAABgABAAAAyQAJAGYAZwABACcAAAAiAAMABAAAAAq7AApZEgu3AAy/AAAAAQAo" +
+    "AAAABgABAAAAzQAJAGgAZwABACcAAAAiAAMABAAAAAq7AApZEgu3AAy/AAAAAQAoAAAABgABAAAA" +
+    "0QAJAGkAagABACcAAAAiAAMABAAAAAq7AApZEgu3AAy/AAAAAQAoAAAABgABAAAA1QAJAGsAagAB" +
+    "ACcAAAAiAAMABAAAAAq7AApZEgu3AAy/AAAAAQAoAAAABgABAAAA2QAJAGwAagABACcAAAAcAAQA" +
+    "BAAAAAQeIGGtAAAAAQAoAAAABgABAAAA4AAJAG0AagABACcAAAAiAAMABAAAAAq7AApZEgu3AAy/" +
+    "AAAAAQAoAAAABgABAAAA5AAJAG4AagABACcAAAAiAAMABAAAAAq7AApZEgu3AAy/AAAAAQAoAAAA" +
+    "BgABAAAA5xBBAGQAbwABACcAAAAhAAIAAgAAAAkqK8AADbYAEKwAAAABACgAAAAGAAEAAAAFAAgA" +
+    "cABxAAEAJwAAACEAAQAAAAAABQGzABGxAAAAAQAoAAAACgACAAAACAAEAAoAAgAcAAAAAgByAHMA" +
+    "AAACAHQ=");
+  private static final byte[] DEX_BYTES = Base64.getDecoder().decode(
+    "ZGV4CjAzNQAFtMupmeDN6Ck5nxdemGsp43KmLNpYLrMYFgAAcAAAAHhWNBIAAAAAAAAAAEgVAABl" +
+    "AAAAcAAAABUAAAAEAgAAIAAAAFgCAAAHAAAA2AMAAD0AAAAQBAAAAQAAAPgFAAAAEAAAGAYAAB4O" +
+    "AAAhDgAAKw4AADMOAAA3DgAAOg4AAEEOAABEDgAARw4AAEoOAABODgAAVg4AAFsOAABfDgAAYg4A" +
+    "AGYOAABrDgAAcA4AAHQOAAB5DgAAfA4AAIAOAACEDgAAiQ4AAI0OAACSDgAAlw4AAJwOAAC7DgAA" +
+    "1w4AAOkOAAD8DgAAEw8AACsPAAA+DwAAUA8AAGQPAACHDwAAmw8AAK8PAADKDwAA4g8AAO0PAAD4" +
+    "DwAAAxAAABsQAAA/EAAAQhAAAEgQAABOEAAAURAAAFUQAABbEAAAXxAAAGIQAABmEAAAahAAAHIQ" +
+    "AAB8EAAAhxAAAJAQAACbEAAArBAAALQQAADEEAAA0RAAAOUQAADtEAAA+RAAAA0RAAAXEQAAIBEA" +
+    "ACoRAAA5EQAAQxEAAE4RAABcEQAAYREAAGYRAAB8EQAAkxEAAJ4RAACxEQAAxBEAAM0RAADbEQAA" +
+    "5xEAAPQRAAAGEgAAEhIAABoSAAAmEgAAKxIAADsSAABIEgAAVxIAAGESAAB3EgAAiRIAAJwSAACj" +
+    "EgAABAAAAAYAAAAHAAAACAAAAA0AAAAbAAAAHAAAAB4AAAAgAAAAIQAAACIAAAAjAAAAJAAAACUA" +
+    "AAAmAAAAJwAAACgAAAAuAAAAMQAAADUAAAA3AAAABAAAAAAAAAAAAAAABgAAAAEAAAAAAAAABwAA" +
+    "AAIAAAAAAAAACAAAAAMAAAAAAAAACQAAAAMAAAC0DQAACgAAAAMAAAC8DQAACwAAAAMAAADMDQAA" +
+    "DAAAAAMAAADUDQAADAAAAAMAAADcDQAADQAAAAQAAAAAAAAADgAAAAQAAAC0DQAADwAAAAQAAADk" +
+    "DQAAEAAAAAQAAADMDQAAEQAAAAQAAADsDQAAEgAAAAQAAAD0DQAAFQAAAAoAAAC0DQAAFwAAAAoA" +
+    "AADsDQAAGAAAAAoAAAD0DQAAGQAAAAoAAAD8DQAAGgAAAAoAAAAEDgAAEwAAAA4AAAAAAAAAFQAA" +
+    "AA4AAAC0DQAAFgAAAA4AAADkDQAAFAAAAA8AAAAMDgAAFwAAAA8AAADsDQAAFQAAABAAAAC0DQAA" +
+    "LgAAABEAAAAAAAAAMQAAABIAAAAAAAAAMgAAABIAAAC0DQAAMwAAABIAAAAUDgAANAAAABIAAADs" +
+    "DQAANgAAABMAAADcDQAACgADAAUAAAAKAAQAKgAAAAoABAArAAAACgADAC8AAAAKAAcAMAAAAAoA" +
+    "BABXAAAACgAEAGMAAAAJAB4AAgAAAAoAGwABAAAACgAcAAIAAAAKAB4AAgAAAAoABAA5AAAACgAA" +
+    "ADoAAAAKAAYAOwAAAAoABwA8AAAACgAIADwAAAAKAAYAPQAAAAoAEAA+AAAACgAMAD8AAAAKAAEA" +
+    "QAAAAAoAHwBCAAAACgACAEMAAAAKAAUARAAAAAoAHQBFAAAACgAQAEYAAAAKABIARgAAAAoAEwBG" +
+    "AAAACgADAEcAAAAKAAQARwAAAAoACgBIAAAACgADAEkAAAAKAAkASgAAAAoACgBLAAAACgAMAEwA" +
+    "AAAKAAwATQAAAAoABABOAAAACgAEAE8AAAAKAA0AUAAAAAoADgBQAAAACgANAFEAAAAKAA4AUQAA" +
+    "AAoADABSAAAACgAKAFMAAAAKAAoAVAAAAAoACwBVAAAACgALAFYAAAAKABoAWAAAAAoABABZAAAA" +
+    "CgAEAFoAAAAKAAwAWwAAAAoAFQBcAAAACgAVAF0AAAAKABUAXgAAAAoAFABfAAAACgAVAF8AAAAK" +
+    "ABYAXwAAAAoAGQBgAAAACgAVAGEAAAAKABYAYQAAAAoAFgBiAAAACgAPAGQAAAAKABAAZAAAAAoA" +
+    "EQBkAAAACwAbAAIAAAAPABsAAgAAAA8AFwA4AAAADwAYADgAAAAPABQAXwAAAAoAAAARAAAACwAA" +
+    "AKwNAAApAAAAVA0AAFEUAABIFAAAAQAAACIUAAABAAAAMhQAAAEAAABAFAAAAAAAAAAAAACsEgAA" +
+    "AQAAAA4AAAAEAAMAAQAAALESAAAGAAAAcBA4AAEAWhIGAA4ABAACAAMAAAC6EgAADgAAABYAAABw" +
+    "MAIAAgEiAAkAGwEsAAAAcCAAABAAJwADAAIAAAAAAMISAAACAAAAElAPAAYABAACAAAAyBIAAAkA" +
+    "AAAiAAkAGwEsAAAAcCAAABAAJwAAAAYABAACAAAA0BIAAAkAAAAiAAkAGwEsAAAAcCAAABAAJwAA" +
+    "AAMAAQACAAAA2BIAAAkAAAAiAAkAGwEsAAAAcCAAABAAJwAAAAYABAACAAAA3xIAAAkAAAAiAAkA" +
+    "GwEsAAAAcCAAABAAJwAAAAgABgACAAAA5xIAAAkAAAAiAAkAGwEsAAAAcCAAABAAJwAAAAYABAAC" +
+    "AAAA8RIAAAkAAAAiAAkAGwEsAAAAcCAAABAAJwAAAAMAAQACAAAA+RIAAAkAAAAiAAkAGwEsAAAA" +
+    "cCAAABAAJwAAAAUAAwACAAAAABMAAAkAAAAiAAkAGwEsAAAAcCAAABAAJwAAAAQAAgACAAAACBMA" +
+    "AAkAAAAiAAkAGwEsAAAAcCAAABAAJwAAAAQAAgACAAAAEBMAAAkAAAAiAAkAGwEsAAAAcCAAABAA" +
+    "JwAAAAQAAgAAAAAAFxMAAAQAAAAWAAEAuyAQAAQAAgAAAAAAHRMAAAUAAAAWAAEAnAACABAAAAAG" +
+    "AAQAAgAAACMTAAAJAAAAIgAJABsBLAAAAHAgAAAQACcAAAAGAAQAAgAAACsTAAAJAAAAIgAJABsB" +
+    "LAAAAHAgAAAQACcAAAAEAAIAAAAAADMTAAAEAAAAmwACAoQADwAEAAIAAAAAADkTAAAGAAAAEhCl" +
+    "AAIAwCCEAA8AAwABAAIAAAA/EwAACQAAACIACQAbASwAAABwIAAAEAAnAAAABAACAAIAAABFEwAA" +
+    "CQAAACIACQAbASwAAABwIAAAEAAnAAAAAwABAAIAAABMEwAACQAAACIACQAbASwAAABwIAAAEAAn" +
+    "AAAABAACAAIAAABSEwAACQAAACIACQAbASwAAABwIAAAEAAnAAAABgAEAAIAAABZEwAACQAAACIA" +
+    "CQAbASwAAABwIAAAEAAnAAAABAACAAAAAABhEwAAAgAAAH0gEAAEAAIAAAAAAGcTAAADAAAAFgAA" +
+    "ABAAAAADAAMAAAAAAG0TAAABAAAAEAAAAAUAAwAAAAAAdBMAAAQAAAAWAAoAvSAQAAMAAgAAAAAA" +
+    "exMAAAIAAAASAA8ABAACAAIAAACBEwAACQAAACIACQAbASwAAABwIAAAEAAnAAAABgAEAAAAAACH" +
+    "EwAAAwAAAJsAAgQQAAAABAACAAIAAACPEwAACQAAACIACQAbASwAAABwIAAAEAAnAAAABAACAAIA" +
+    "AACVEwAACQAAACIACQAbASwAAABwIAAAEAAnAAAABAACAAIAAACbEwAACQAAACIACQAbASwAAABw" +
+    "IAAAEAAnAAAABAACAAIAAAChEwAACQAAACIACQAbASwAAABwIAAAEAAnAAAABQADAAIAAACnEwAA" +
+    "CQAAACIACQAbASwAAABwIAAAEAAnAAAABAACAAIAAACuEwAACQAAACIACQAbASwAAABwIAAAEAAn" +
+    "AAAABAACAAIAAAC0EwAACQAAACIACQAbASwAAABwIAAAEAAnAAAABQADAAIAAAC6EwAACQAAACIA" +
+    "CQAbASwAAABwIAAAEAAnAAAABQADAAIAAADBEwAACQAAACIACQAbASwAAABwIAAAEAAnAAAABAAC" +
+    "AAMAAADIEwAABgAAACIACgBwMAIAIAMRAAMAAQACAAAAzxMAAAkAAAAiAAkAGwEsAAAAcCAAABAA" +
+    "JwAAAAQAAgACAAAA1hMAAAkAAAAiAAkAGwEsAAAAcCAAABAAJwAAAAMAAQACAAAA3hMAAAkAAAAi" +
+    "AAkAGwEsAAAAcCAAABAAJwAAAAQAAgACAAAA5BMAAAkAAAAiAAkAGwEsAAAAcCAAABAAJwAAAAMA" +
+    "AgACAAAA6xMAAAcAAAAfAgoAbiAHACEACgAPAAAAAwABAAIAAADyEwAACQAAACIACQAbASwAAABw" +
+    "IAAAEAAnAAAABAACAAIAAAD4EwAACQAAACIACQAbASwAAABwIAAAEAAnAAAAAwABAAIAAAD/EwAA" +
+    "CQAAACIACQAbASwAAABwIAAAEAAnAAAAAwABAAIAAAAFFAAACQAAACIACQAbASwAAABwIAAAEAAn" +
+    "AAAAAwABAAIAAAALFAAACQAAACIACQAbASwAAABwIAAAEAAnAAAAAwABAAAAAAARFAAAAwAAAFMg" +
+    "BgAQAAAAAwABAAIAAAAXFAAACQAAACIACQAbASwAAABwIAAAEAAnAAAABQABAAMAAAAdFAAAGAAA" +
+    "ACIADwBwEDkAAAAbAS0AAABuIDsAEAAMAFNCBgCGIm4wOgAgAwwAbhA8AAAADAARABgGAAABAAAA" +
+    "CAAAAAAAAAAEAAAAIAYAAAMAAAAoBgAACgAAACgGAAAeAAAAKAYAAB8AAAAoBgAAIAAAACgGAAAh" +
+    "AAAAKAYAADYAAAAoBgAANwAAACgGAAABAAAACAAAAAEAAAAEAAAABQAAAAQAAwAUAAMAAwAAAAIA" +
+    "AAAEAAQAAQAAAAoAAAABAAAADQAAAAIAAAAEAAMAAQAAAA4AAAACAAAADgADAAIAAAAOAAQAAgAA" +
+    "AA4ACgABAAAAAQAAAAMAAAAEAAMAFAABPAAIPGNsaW5pdD4ABjxpbml0PgACPjsAAUIABUJZVEVT" +
+    "AAFEAAFGAAFJAAJJSgAGSUpJTElJAANJSkoAAklMAAFKAAJKSgADSkpJAANKSkoAAkpMAANKTEkA" +
+    "AUwAAkxEAAJMSgADTEpJAAJMTAADTExJAANMTEoAA0xMTAAdTGRhbHZpay9hbm5vdGF0aW9uL1Np" +
+    "Z25hdHVyZTsAGkxkYWx2aWsvYW5ub3RhdGlvbi9UaHJvd3M7ABBMamF2YS9sYW5nL0NsYXNzABFM" +
+    "amF2YS9sYW5nL0NsYXNzOwAVTGphdmEvbGFuZy9Db21wYXJhYmxlABZMamF2YS9sYW5nL0NvbXBh" +
+    "cmFibGU7ABFMamF2YS9sYW5nL0Vycm9yOwAQTGphdmEvbGFuZy9Mb25nOwASTGphdmEvbGFuZy9O" +
+    "dW1iZXI7ACFMamF2YS9sYW5nL051bWJlckZvcm1hdEV4Y2VwdGlvbjsAEkxqYXZhL2xhbmcvT2Jq" +
+    "ZWN0OwASTGphdmEvbGFuZy9TdHJpbmc7ABlMamF2YS9sYW5nL1N0cmluZ0J1aWxkZXI7ABZMamF2" +
+    "YS9tYXRoL0JpZ0ludGVnZXI7AAlMb25nLmphdmEACU1BWF9WQUxVRQAJTUlOX1ZBTFVFABZNZXRo" +
+    "b2QgcmVkZWZpbmVkIGF3YXkhACJSZWRlZmluZWQgTG9uZyEgdmFsdWUgKGFzIGRvdWJsZSk9AAFT" +
+    "AARTSVpFAARUWVBFAAFWAAJWSgAEVkpJTAACVkwAAVoAAlpMAAJbQwAGYXBwZW5kAAhiaXRDb3Vu" +
+    "dAAJYnl0ZVZhbHVlAAdjb21wYXJlAAljb21wYXJlVG8AD2NvbXBhcmVVbnNpZ25lZAAGZGVjb2Rl" +
+    "AA5kaXZpZGVVbnNpZ25lZAALZG91YmxlVmFsdWUAEmVtaXR0ZXI6IGphY2stNC4yNQAGZXF1YWxz" +
+    "AApmbG9hdFZhbHVlABJmb3JtYXRVbnNpZ25lZExvbmcACGdldENoYXJzAAdnZXRMb25nAAhoYXNo" +
+    "Q29kZQANaGlnaGVzdE9uZUJpdAAIaW50VmFsdWUACWxvbmdWYWx1ZQAMbG93ZXN0T25lQml0AANt" +
+    "YXgAA21pbgAUbnVtYmVyT2ZMZWFkaW5nWmVyb3MAFW51bWJlck9mVHJhaWxpbmdaZXJvcwAJcGFy" +
+    "c2VMb25nABFwYXJzZVVuc2lnbmVkTG9uZwARcmVtYWluZGVyVW5zaWduZWQAB3JldmVyc2UADHJl" +
+    "dmVyc2VCeXRlcwAKcm90YXRlTGVmdAALcm90YXRlUmlnaHQAEHNlcmlhbFZlcnNpb25VSUQACnNo" +
+    "b3J0VmFsdWUABnNpZ251bQAKc3RyaW5nU2l6ZQADc3VtAA50b0JpbmFyeVN0cmluZwALdG9IZXhT" +
+    "dHJpbmcADXRvT2N0YWxTdHJpbmcACHRvU3RyaW5nABR0b1Vuc2lnbmVkQmlnSW50ZWdlcgAQdG9V" +
+    "bnNpZ25lZFN0cmluZwARdG9VbnNpZ25lZFN0cmluZzAABXZhbHVlAAd2YWx1ZU9mAAUABw4AkAEB" +
+    "AAcOPC0AlQEBAAcOWgAiAQAHDgDNAQIAAAcOANEBAgAABw4AiwEBAAcOANUBAgAABw4AXQUAAAAA" +
+    "AAcOAGkDAAAABw4AvgEBAAcOAMIBAgAABw4AxgECAAAHDgC2AQEABw4ADgEABw4AEwEABw4A5AEC" +
+    "AAAHDgDnAQIAAAcOABgBAAcOAB0BAAcOAHUBAAcOAHECAAAHDgB9AQAHDgB5AgAABw4A2QECAAAH" +
+    "DgAxAQAHDgA7AQAHDgAnAgAABw4ALAIAAAcOADYBAAcOAG0BAAcOAOABAgAABw4AVQEABw4ATQEA" +
+    "Bw4AUQEABw4AYQEABw4AQwIAAAcOAEoBAAcOAGUBAAcOAEYCAAAHDgBZAgAABw4AhwEBAAcOAIQB" +
+    "AQAHDgCBAQIAAAcOAJoBAAcOAMkBAQAHDgDIAQEABw4ArgEABw4AugEBAAcOAKoBAAcOALIBAAcO" +
+    "AKIBAAcOAKYBAAcOAJ4BAAcOAD8ABw4AAgUBYxwFFyMXHxcAFyIXAwIFAWMcBBcdFwAXIhcDAgYB" +
+    "YxwBGAwEBAgGAAYABEAGASwLABkBGQEZARkBGQEaBhIBiIAEsAwBgYAExAwBgYAE4AwBCYwNAgmg" +
+    "DQMJxA0BCegNAQmMDgQIsA4BCNQOAQn4DgEJnA8BCcAPAgnkDwEJiBADCaAQAQm8EAEJ4BABCYQR" +
+    "AQmcEQEJuBEBCdwRAQmAEgEJpBIBCcgSAQnsEgEJgBMBCZgTAQmsEwIJxBMBCNgTAQn8EwEJlBQB" +
+    "CbgUAQncFAIJgBUBCaQVAQrIFQEJ7BUBCZAWAQi0FgEJ2BYBCfQWAQmYFwUBvBcCAeAXAcEghBgE" +
+    "AaQYAQHIGAEB7BgGAZAZAwG0GQEB2BkPAfAZBwGUGgAAEQAAAAAAAAABAAAAAAAAAAEAAABlAAAA" +
+    "cAAAAAIAAAAVAAAABAIAAAMAAAAgAAAAWAIAAAQAAAAHAAAA2AMAAAUAAAA9AAAAEAQAAAYAAAAB" +
+    "AAAA+AUAAAMQAAADAAAAGAYAAAEgAAA3AAAAMAYAAAYgAAABAAAAVA0AAAEQAAANAAAArA0AAAIg" +
+    "AABlAAAAHg4AAAMgAAA3AAAArBIAAAQgAAADAAAAIhQAAAUgAAABAAAASBQAAAAgAAABAAAAURQA" +
+    "AAAQAAABAAAASBUAAA==");
+
+  static class FuncCmp implements LongPredicate {
+    final String name;
+    final LongPredicate p;
+    public FuncCmp(String name, LongPredicate p) {
+      this.name = name;
+      this.p = p;
+    }
+
+    public boolean test(long l) {
+      return p.test(l);
+    }
+  }
+  static FuncCmp l2l(String name, final LongUnaryOperator a, final LongUnaryOperator b) {
+    return new FuncCmp(name, (v) -> a.applyAsLong(v) == b.applyAsLong(v));
+  }
+  static FuncCmp l2i(String name, final LongToIntFunction a, final LongToIntFunction b) {
+    return new FuncCmp(name, (v) -> a.applyAsInt(v) == b.applyAsInt(v));
+  }
+
+  /** Interface for a long, int -> long function. */
+  static interface LI2IFunction { public long applyToLongInt(long a, int b); }
+
+  static FuncCmp li2l(String name, final Random r, final LI2IFunction a, final LI2IFunction b) {
+    return new FuncCmp(name, new LongPredicate() {
+      public boolean test(long v) {
+        int i = r.nextInt();
+        return a.applyToLongInt(v, i) == b.applyToLongInt(v, i);
+      }
+    });
+  }
+
+  public static void main(String[] args) {
+    doTest(10000);
+  }
+
+  public static void doTest(int iters) {
+    // Just transform immediately.
+    doCommonClassRedefinition(Long.class, CLASS_BYTES, DEX_BYTES);
+    final Random r = new Random();
+    FuncCmp[] comps = new FuncCmp[] {
+      l2l("highestOneBit", Long::highestOneBit, RedefinedLongIntrinsics::highestOneBit),
+      l2l("lowestOneBit", Long::lowestOneBit, RedefinedLongIntrinsics::lowestOneBit),
+      l2i("numberOfLeadingZeros",
+          Long::numberOfLeadingZeros,
+          RedefinedLongIntrinsics::numberOfLeadingZeros),
+      l2i("numberOfTrailingZeros",
+          Long::numberOfTrailingZeros,
+          RedefinedLongIntrinsics::numberOfTrailingZeros),
+      l2i("bitCount", Long::bitCount, RedefinedLongIntrinsics::bitCount),
+      li2l("rotateLeft", r, Long::rotateLeft, RedefinedLongIntrinsics::rotateLeft),
+      li2l("rotateRight", r, Long::rotateRight, RedefinedLongIntrinsics::rotateRight),
+      l2l("reverse", Long::reverse, RedefinedLongIntrinsics::reverse),
+      l2i("signum", Long::signum, RedefinedLongIntrinsics::signum),
+      l2l("reverseBytes", Long::reverseBytes, RedefinedLongIntrinsics::reverseBytes),
+    };
+    for (FuncCmp f : comps) {
+      // Just actually use ints so we can cast them back int the tests to print them (since we
+      // deleted a bunch of the Long methods needed for printing longs)!
+      int failures = (int)r.ints(iters)
+                           .mapToLong((v) -> (long)v)
+                           .filter(f.negate()) // Get all the test cases that failed.
+                           .count();
+      if (failures != 0) {
+        double percent = 100.0d*((double)failures/(double)iters);
+        System.out.println("for intrinsic " + f.name + " " + failures + "/" + iters
+            + " (" + Double.toString(percent) + "%) tests failed!");
+      }
+    }
+    System.out.println("Finished!");
+  }
+
+  // Transforms the class
+  private static native void doCommonClassRedefinition(Class<?> target,
+                                                       byte[] class_file,
+                                                       byte[] dex_file);
+}
diff --git a/test/950-redefine-intrinsic/src/RedefinedLongIntrinsics.java b/test/950-redefine-intrinsic/src/RedefinedLongIntrinsics.java
new file mode 100644
index 0000000..0ada4a6
--- /dev/null
+++ b/test/950-redefine-intrinsic/src/RedefinedLongIntrinsics.java
@@ -0,0 +1,70 @@
+/*
+ * 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.
+ */
+
+/**
+ * The methods that are intrinsified in Long and their expected redefined implementations.
+ */
+class RedefinedLongIntrinsics {
+  // Intrinsic! Do something cool. Return i + 1
+  public static long highestOneBit(long i) {
+    return i + 1;
+  }
+
+  // Intrinsic! Do something cool. Return i - 1
+  public static long lowestOneBit(long i) {
+    return i - 1;
+  }
+
+  // Intrinsic! Do something cool. Return i + i
+  public static int numberOfLeadingZeros(long i) {
+    return (int)(i + i);
+  }
+
+  // Intrinsic! Do something cool. Return i & (i >>> 1);
+  public static int numberOfTrailingZeros(long i) {
+    return (int)(i & (i >>> 1));
+  }
+
+  // Intrinsic! Do something cool. Return 5
+  public static int bitCount(long i) {
+    return 5;
+  }
+
+  // Intrinsic! Do something cool. Return i
+  public static long rotateLeft(long i, int distance) {
+    return i;
+  }
+
+  // Intrinsic! Do something cool. Return 10 * i
+  public static long rotateRight(long i, int distance) {
+    return 10 * i;
+  }
+
+  // Intrinsic! Do something cool. Return -i
+  public static long reverse(long i) {
+    return -i;
+  }
+
+  // Intrinsic! Do something cool. Return 0
+  public static int signum(long i) {
+    return 0;
+  }
+
+  // Intrinsic! Do something cool. Return 0
+  public static long reverseBytes(long i) {
+    return 0;
+  }
+}
diff --git a/test/Android.run-test.mk b/test/Android.run-test.mk
index c14a0b2..95967b5 100644
--- a/test/Android.run-test.mk
+++ b/test/Android.run-test.mk
@@ -170,12 +170,6 @@
 ifeq ($(ART_TEST_RUN_TEST_MULTI_IMAGE),true)
   IMAGE_TYPES := multipicimage
 endif
-ifeq ($(ART_TEST_NPIC_IMAGE),true)
-  IMAGE_TYPES += npicimage
-  ifeq ($(ART_TEST_RUN_TEST_MULTI_IMAGE),true)
-    IMAGE_TYPES := multinpicimage
-  endif
-endif
 PICTEST_TYPES := npictest
 ifeq ($(ART_TEST_PIC_TEST),true)
   PICTEST_TYPES += pictest
@@ -878,21 +872,13 @@
     endif
   endif
   ifeq ($(2),no-image)
-    $(1)_prereq_rules += $$($(call name-to-var,$(1))_CORE_IMAGE_$$(image_suffix)_pic_$(4))
+    $(1)_prereq_rules += $$($(call name-to-var,$(1))_CORE_IMAGE_$$(image_suffix)_$(4))
   else
-    ifeq ($(2),npicimage)
-      $(1)_prereq_rules += $$($(call name-to-var,$(1))_CORE_IMAGE_$$(image_suffix)_no-pic_$(4))
+    ifeq ($(2),picimage)
+      $(1)_prereq_rules += $$($(call name-to-var,$(1))_CORE_IMAGE_$$(image_suffix)_$(4))
     else
-      ifeq ($(2),picimage)
-        $(1)_prereq_rules += $$($(call name-to-var,$(1))_CORE_IMAGE_$$(image_suffix)_pic_$(4))
-      else
-        ifeq ($(2),multinpicimage)
-          $(1)_prereq_rules += $$($(call name-to-var,$(1))_CORE_IMAGE_$$(image_suffix)_no-pic_multi_$(4))
-        else
-          ifeq ($(2),multipicimage)
-             $(1)_prereq_rules += $$($(call name-to-var,$(1))_CORE_IMAGE_$$(image_suffix)_pic_multi_$(4))
-          endif
-        endif
+      ifeq ($(2),multipicimage)
+        $(1)_prereq_rules += $$($(call name-to-var,$(1))_CORE_IMAGE_$$(image_suffix)_multi_$(4))
       endif
     endif
   endif
@@ -1093,50 +1079,29 @@
     # Add the core dependency. This is required for pre-building.
     # Use the PIC image, as it is the default in run-test, to match dependencies.
     ifeq ($(1),host)
-      prereq_rule += $$(HOST_CORE_IMAGE_$$(image_suffix)_pic_$(13))
+      prereq_rule += $$(HOST_CORE_IMAGE_$$(image_suffix)_$(13))
     else
-      prereq_rule += $$(TARGET_CORE_IMAGE_$$(image_suffix)_pic_$(13))
+      prereq_rule += $$(TARGET_CORE_IMAGE_$$(image_suffix)_$(13))
     endif
   else
-    ifeq ($(9),npicimage)
-      test_groups += ART_RUN_TEST_$$(uc_host_or_target)_IMAGE_RULES
-      run_test_options += --npic-image
-      # Add the core dependency.
+    ifeq ($(9),picimage)
+      test_groups += ART_RUN_TEST_$$(uc_host_or_target)_PICIMAGE_RULES
       ifeq ($(1),host)
-        prereq_rule += $$(HOST_CORE_IMAGE_$$(image_suffix)_no-pic_$(13))
+        prereq_rule += $$(HOST_CORE_IMAGE_$$(image_suffix)_$(13))
       else
-        prereq_rule += $$(TARGET_CORE_IMAGE_$$(image_suffix)_no-pic_$(13))
+        prereq_rule += $$(TARGET_CORE_IMAGE_$$(image_suffix)_$(13))
       endif
     else
-      ifeq ($(9),picimage)
+      ifeq ($(9),multipicimage)
         test_groups += ART_RUN_TEST_$$(uc_host_or_target)_PICIMAGE_RULES
+        run_test_options += --multi-image
         ifeq ($(1),host)
-          prereq_rule += $$(HOST_CORE_IMAGE_$$(image_suffix)_pic_$(13))
+          prereq_rule += $$(HOST_CORE_IMAGE_$$(image_suffix)_multi_$(13))
         else
-          prereq_rule += $$(TARGET_CORE_IMAGE_$$(image_suffix)_pic_$(13))
+          prereq_rule += $$(TARGET_CORE_IMAGE_$$(image_suffix)_multi_$(13))
         endif
       else
-        ifeq ($(9),multinpicimage)
-          test_groups += ART_RUN_TEST_$$(uc_host_or_target)_IMAGE_RULES
-          run_test_options += --npic-image --multi-image
-                ifeq ($(1),host)
-                        prereq_rule += $$(HOST_CORE_IMAGE_$$(image_suffix)_no-pic_multi_$(13))
-                else
-                        prereq_rule += $$(TARGET_CORE_IMAGE_$$(image_suffix)_no-pic_multi_$(13))
-                endif
-        else
-          ifeq ($(9),multipicimage)
-            test_groups += ART_RUN_TEST_$$(uc_host_or_target)_PICIMAGE_RULES
-                        run_test_options += --multi-image
-                        ifeq ($(1),host)
-                        prereq_rule += $$(HOST_CORE_IMAGE_$$(image_suffix)_pic_multi_$(13))
-                        else
-                        prereq_rule += $$(TARGET_CORE_IMAGE_$$(image_suffix)_pic_multi_$(13))
-                        endif
-          else
-            $$(error found $(9) expected $(IMAGE_TYPES))
-          endif
-        endif
+        $$(error found $(9) expected $(IMAGE_TYPES))
       endif
     endif
   endif
diff --git a/test/etc/run-test-jar b/test/etc/run-test-jar
index 7d218f1..f3d4332 100755
--- a/test/etc/run-test-jar
+++ b/test/etc/run-test-jar
@@ -19,7 +19,7 @@
 DEV_MODE="n"
 DEX2OAT=""
 EXPERIMENTAL=""
-FALSE_BIN="/system/bin/false"
+FALSE_BIN="false"
 FLAGS=""
 ANDROID_FLAGS=""
 GDB=""
diff --git a/test/run-test b/test/run-test
index d55ba77..e808dee 100755
--- a/test/run-test
+++ b/test/run-test
@@ -80,7 +80,7 @@
 
 # ANDROID_HOST_OUT is not set in a build environment.
 if [ -z "$ANDROID_HOST_OUT" ]; then
-    export ANDROID_HOST_OUT=$ANDROID_BUILD_TOP/out/host/linux-x86
+    export ANDROID_HOST_OUT=${OUT_DIR:-$ANDROID_BUILD_TOP/out/}host/linux-x86
 fi
 
 # If JACK_CLASSPATH is not set, assume it only contains core-libart.
@@ -143,7 +143,6 @@
 have_dex2oat="yes"
 have_patchoat="yes"
 have_image="yes"
-pic_image_suffix=""
 multi_image_suffix=""
 android_root="/system"
 bisection_search="no"
@@ -199,9 +198,6 @@
     elif [ "x$1" = "x--no-image" ]; then
         have_image="no"
         shift
-    elif [ "x$1" = "x--npic-image" ]; then
-        pic_image_suffix="-npic"
-        shift
     elif [ "x$1" = "x--multi-image" ]; then
         multi_image_suffix="-multi"
         shift
@@ -507,12 +503,12 @@
 elif [ "$runtime" = "art" ]; then
     if [ "$target_mode" = "no" ]; then
         guess_host_arch_name
-        run_args="${run_args} --boot ${ANDROID_HOST_OUT}/framework/core${image_suffix}${pic_image_suffix}${multi_image_suffix}.art"
+        run_args="${run_args} --boot ${ANDROID_HOST_OUT}/framework/core${image_suffix}${multi_image_suffix}.art"
         run_args="${run_args} --runtime-option -Djava.library.path=${ANDROID_HOST_OUT}/lib${suffix64}:${ANDROID_HOST_OUT}/nativetest${suffix64}"
     else
         guess_target_arch_name
         run_args="${run_args} --runtime-option -Djava.library.path=/data/nativetest${suffix64}/art/${target_arch_name}"
-        run_args="${run_args} --boot /data/art-test/core${image_suffix}${pic_image_suffix}${multi_image_suffix}.art"
+        run_args="${run_args} --boot /data/art-test/core${image_suffix}${multi_image_suffix}.art"
     fi
     if [ "$relocate" = "yes" ]; then
       run_args="${run_args} --relocate"
@@ -668,8 +664,6 @@
         echo "    --dex2oat-swap        Use a dex2oat swap file."
         echo "    --instruction-set-features [string]"
         echo "                          Set instruction-set-features for compilation."
-        echo "    --npic-image          Use an image compiled with non-position independent code "
-        echo "                          for the boot class path."
         echo "    --multi-image         Use a set of images compiled with dex2oat multi-image for"
         echo "                          the boot class path."
         echo "    --pic-test            Compile the test code position independent."
diff --git a/test/testrunner/env.py b/test/testrunner/env.py
index 0b69718..4336d77 100644
--- a/test/testrunner/env.py
+++ b/test/testrunner/env.py
@@ -105,9 +105,6 @@
 # Do you want to test the optimizing compiler with graph coloring register allocation?
 ART_TEST_OPTIMIZING_GRAPH_COLOR = getEnvBoolean('ART_TEST_OPTIMIZING_GRAPH_COLOR', ART_TEST_FULL)
 
-# Do we want to test a non-PIC-compiled core image?
-ART_TEST_NPIC_IMAGE = getEnvBoolean('ART_TEST_NPIC_IMAGE', ART_TEST_FULL)
-
 # Do we want to test PIC-compiled tests ("apps")?
 ART_TEST_PIC_TEST = getEnvBoolean('ART_TEST_PIC_TEST', ART_TEST_FULL)
 # Do you want tracing tests run?
@@ -179,6 +176,8 @@
 
 ART_TEST_WITH_STRACE = getEnvBoolean('ART_TEST_DEBUG_GC', False)
 
+EXTRA_DISABLED_TESTS = set(env.get("ART_TEST_RUN_TEST_SKIP", "").split())
+
 TARGET_2ND_ARCH = get_build_var('TARGET_2ND_ARCH')
 TARGET_ARCH = get_build_var('TARGET_ARCH')
 if TARGET_2ND_ARCH:
diff --git a/test/testrunner/testrunner.py b/test/testrunner/testrunner.py
index 5a6114e..8c0b928 100755
--- a/test/testrunner/testrunner.py
+++ b/test/testrunner/testrunner.py
@@ -44,10 +44,10 @@
 In the end, the script will print the failed and skipped tests if any.
 
 """
+import argparse
 import fnmatch
 import itertools
 import json
-from optparse import OptionParser
 import os
 import re
 import subprocess
@@ -133,8 +133,7 @@
   VARIANT_TYPE_DICT['run'] = {'ndebug', 'debug'}
   VARIANT_TYPE_DICT['target'] = {'target', 'host'}
   VARIANT_TYPE_DICT['trace'] = {'trace', 'ntrace', 'stream'}
-  VARIANT_TYPE_DICT['image'] = {'picimage', 'no-image', 'npicimage',
-                                'multinpicimage', 'multipicimage'}
+  VARIANT_TYPE_DICT['image'] = {'picimage', 'no-image', 'multipicimage'}
   VARIANT_TYPE_DICT['debuggable'] = {'ndebuggable', 'debuggable'}
   VARIANT_TYPE_DICT['gc'] = {'gcstress', 'gcverify', 'cms'}
   VARIANT_TYPE_DICT['prebuild'] = {'no-prebuild', 'no-dex2oat', 'prebuild'}
@@ -218,10 +217,6 @@
     IMAGE_TYPES.add('no-image')
   if env.ART_TEST_RUN_TEST_MULTI_IMAGE:
     IMAGE_TYPES.add('multipicimage')
-  if env.ART_TEST_NPIC_IMAGE:
-    IMAGE_TYPES.add('npicimage')
-  if env.ART_TEST_RUN_TEST_MULTI_IMAGE:
-    IMAGE_TYPES.add('multinpicimage')
   if env.ART_TEST_RUN_TEST_IMAGE or not IMAGE_TYPES: # Default
     IMAGE_TYPES.add('picimage')
 
@@ -388,10 +383,6 @@
 
       if image == 'no-image':
         options_test += ' --no-image'
-      elif image == 'npicimage':
-        options_test += ' --npic-image'
-      elif image == 'multinpicimage':
-        options_test += ' --npic-image --multi-image'
       elif image == 'multipicimage':
         options_test += ' --multi-image'
 
@@ -605,6 +596,8 @@
   """
   if dry_run:
     return True
+  if test in env.EXTRA_DISABLED_TESTS:
+    return True
   variants_list = DISABLED_TEST_CONTAINER.get(test, {})
   for variants in variants_list:
     variants_present = True
@@ -720,23 +713,26 @@
   global gdb
   global gdb_arg
 
-  parser = OptionParser()
-  parser.add_option('-t', '--test', dest='test', help='name of the test')
-  parser.add_option('-j', type='int', dest='n_thread')
+  parser = argparse.ArgumentParser(description="Runs all or a subset of the ART test suite.")
+  parser.add_argument('-t', '--test', dest='test', help='name of the test')
+  parser.add_argument('-j', type=int, dest='n_thread')
   for variant in TOTAL_VARIANTS_SET:
     flag = '--' + variant
     flag_dest = variant.replace('-', '_')
     if variant == '32' or variant == '64':
       flag_dest = 'n' + flag_dest
-    parser.add_option(flag, action='store_true', dest=flag_dest)
-  parser.add_option('--verbose', '-v', action='store_true', dest='verbose')
-  parser.add_option('--dry-run', action='store_true', dest='dry_run')
-  parser.add_option('-b', '--build-dependencies', action='store_true', dest='build')
-  parser.add_option('--gdb', action='store_true', dest='gdb')
-  parser.add_option('--gdb-arg', dest='gdb_arg')
+    parser.add_argument(flag, action='store_true', dest=flag_dest)
+  parser.add_argument('--verbose', '-v', action='store_true', dest='verbose')
+  parser.add_argument('--dry-run', action='store_true', dest='dry_run')
+  parser.add_argument("--skip", action="append", dest="skips", default=[],
+                      help="Skip the given test in all circumstances.")
+  parser.add_argument('-b', '--build-dependencies', action='store_true', dest='build')
+  parser.add_argument('--gdb', action='store_true', dest='gdb')
+  parser.add_argument('--gdb-arg', dest='gdb_arg')
 
-  options = parser.parse_args()[0]
+  options = parser.parse_args()
   test = ''
+  env.EXTRA_DISABLED_TESTS.update(set(options.skips))
   if options.test:
     test = parse_test_name(options.test)
   if options.pictest:
@@ -799,10 +795,6 @@
     TRACE_TYPES.add('ntrace')
   if options.cms:
     GC_TYPES.add('cms')
-  if options.npicimage:
-    IMAGE_TYPES.add('npicimage')
-  if options.multinpicimage:
-    IMAGE_TYPES.add('multinpicimage')
   if options.multipicimage:
     IMAGE_TYPES.add('multipicimage')
   if options.verbose:
@@ -823,10 +815,8 @@
   return test
 
 def main():
-  global verbose
   gather_test_info()
   user_requested_test = parse_option()
-  verbose = True
   setup_test_env()
   if build:
     build_targets = ''
diff --git a/tools/ahat/Android.mk b/tools/ahat/Android.mk
index 493eafb..f79377d 100644
--- a/tools/ahat/Android.mk
+++ b/tools/ahat/Android.mk
@@ -23,7 +23,6 @@
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 LOCAL_JAR_MANIFEST := src/manifest.txt
 LOCAL_JAVA_RESOURCE_FILES := \
-  $(LOCAL_PATH)/src/help.html \
   $(LOCAL_PATH)/src/style.css
 
 LOCAL_STATIC_JAVA_LIBRARIES := perflib-prebuilt guavalib trove-prebuilt
@@ -79,8 +78,9 @@
 # BUILD_HOST_DALVIK_JAVA_LIBRARY above.
 AHAT_TEST_DUMP_JAR := $(LOCAL_BUILT_MODULE)
 AHAT_TEST_DUMP_HPROF := $(intermediates.COMMON)/test-dump.hprof
+AHAT_TEST_DUMP_BASE_HPROF := $(intermediates.COMMON)/test-dump-base.hprof
 
-# Run ahat-test-dump.jar to generate test-dump.hprof
+# Run ahat-test-dump.jar to generate test-dump.hprof and test-dump-base.hprof
 AHAT_TEST_DUMP_DEPENDENCIES := \
   $(ART_HOST_EXECUTABLES) \
   $(ART_HOST_SHARED_LIBRARY_DEPENDENCIES) \
@@ -93,12 +93,19 @@
 $(AHAT_TEST_DUMP_HPROF): $(AHAT_TEST_DUMP_JAR) $(AHAT_TEST_DUMP_DEPENDENCIES)
 	$(PRIVATE_AHAT_TEST_ART) -cp $(PRIVATE_AHAT_TEST_DUMP_JAR) Main $@
 
+$(AHAT_TEST_DUMP_BASE_HPROF): PRIVATE_AHAT_TEST_ART := $(HOST_OUT_EXECUTABLES)/art
+$(AHAT_TEST_DUMP_BASE_HPROF): PRIVATE_AHAT_TEST_DUMP_JAR := $(AHAT_TEST_DUMP_JAR)
+$(AHAT_TEST_DUMP_BASE_HPROF): PRIVATE_AHAT_TEST_DUMP_DEPENDENCIES := $(AHAT_TEST_DUMP_DEPENDENCIES)
+$(AHAT_TEST_DUMP_BASE_HPROF): $(AHAT_TEST_DUMP_JAR) $(AHAT_TEST_DUMP_DEPENDENCIES)
+	$(PRIVATE_AHAT_TEST_ART) -cp $(PRIVATE_AHAT_TEST_DUMP_JAR) Main $@ --base
+
 .PHONY: ahat-test
 ahat-test: PRIVATE_AHAT_TEST_DUMP_HPROF := $(AHAT_TEST_DUMP_HPROF)
+ahat-test: PRIVATE_AHAT_TEST_DUMP_BASE_HPROF := $(AHAT_TEST_DUMP_BASE_HPROF)
 ahat-test: PRIVATE_AHAT_TEST_JAR := $(AHAT_TEST_JAR)
 ahat-test: PRIVATE_AHAT_PROGUARD_MAP := $(AHAT_TEST_DUMP_PROGUARD_MAP)
-ahat-test: $(AHAT_TEST_JAR) $(AHAT_TEST_DUMP_HPROF)
-	java -Dahat.test.dump.hprof=$(PRIVATE_AHAT_TEST_DUMP_HPROF) -Dahat.test.dump.map=$(PRIVATE_AHAT_PROGUARD_MAP) -jar $(PRIVATE_AHAT_TEST_JAR)
+ahat-test: $(AHAT_TEST_JAR) $(AHAT_TEST_DUMP_HPROF) $(AHAT_TEST_DUMP_BASE_HPROF)
+	java -enableassertions -Dahat.test.dump.hprof=$(PRIVATE_AHAT_TEST_DUMP_HPROF) -Dahat.test.dump.base.hprof=$(PRIVATE_AHAT_TEST_DUMP_BASE_HPROF) -Dahat.test.dump.map=$(PRIVATE_AHAT_PROGUARD_MAP) -jar $(PRIVATE_AHAT_TEST_JAR)
 
 # Clean up local variables.
 AHAT_TEST_DUMP_DEPENDENCIES :=
diff --git a/tools/ahat/README.txt b/tools/ahat/README.txt
index 8dfb4ab..133426f 100644
--- a/tools/ahat/README.txt
+++ b/tools/ahat/README.txt
@@ -1,22 +1,21 @@
 AHAT - Android Heap Analysis Tool
 
 Usage:
-  java -jar ahat.jar [-p port] [--proguard-map FILE] FILE
-    Launch an http server for viewing the given Android heap-dump FILE.
+  java -jar ahat.jar [OPTIONS] FILE
+    Launch an http server for viewing the given Android heap dump FILE.
 
-  Options:
+  OPTIONS:
     -p <port>
        Serve pages on the given port. Defaults to 7100.
     --proguard-map FILE
        Use the proguard map FILE to deobfuscate the heap dump.
+    --baseline FILE
+       Diff the heap dump against the given baseline heap dump FILE.
+    --baseline-proguard-map FILE
+       Use the proguard map FILE to deobfuscate the baseline heap dump.
 
 TODO:
- * Have a way to diff two heap dumps.
-
- * Add more tips to the help page.
-   - Recommend how to start looking at a heap dump.
-   - Say how to enable allocation sites.
-   - Where to submit feedback, questions, and bug reports.
+ * Add a user guide.
  * Dim 'image' and 'zygote' heap sizes slightly? Why do we even show these?
  * Let user re-sort sites objects info by clicking column headers.
  * Let user re-sort "Objects" list.
@@ -49,9 +48,9 @@
    time.
  * That we don't show the 'extra' column in the DominatedList if we are
    showing all the instances.
- * That InstanceUtils.asString properly takes into account "offset" and
+ * That Instance.asString properly takes into account "offset" and
    "count" fields, if they are present.
- * InstanceUtils.getDexCacheLocation
+ * Instance.getDexCacheLocation
 
 Reported Issues:
  * Request to be able to sort tables by size.
@@ -76,7 +75,14 @@
  * Instance.isRoot and Instance.getRootTypes.
 
 Release History:
- 0.9 Pending
+ 1.1 Feb 21, 2017
+   Show java.lang.ref.Reference referents as "unreachable" instead of null.
+
+ 1.0 Dec 20, 2016
+   Add support for diffing two heap dumps.
+   Remove native allocations view.
+   Remove outdated help page.
+   Significant refactoring of ahat internals.
 
  0.8 Oct 18, 2016
    Show sample path from GC root with field names in place of dominator path.
diff --git a/tools/ahat/src/AhatSnapshot.java b/tools/ahat/src/AhatSnapshot.java
deleted file mode 100644
index ba8243f..0000000
--- a/tools/ahat/src/AhatSnapshot.java
+++ /dev/null
@@ -1,287 +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.
- */
-
-package com.android.ahat;
-
-import com.android.tools.perflib.captures.MemoryMappedFileBuffer;
-import com.android.tools.perflib.heap.ClassObj;
-import com.android.tools.perflib.heap.Heap;
-import com.android.tools.perflib.heap.Instance;
-import com.android.tools.perflib.heap.ProguardMap;
-import com.android.tools.perflib.heap.RootObj;
-import com.android.tools.perflib.heap.RootType;
-import com.android.tools.perflib.heap.Snapshot;
-import com.android.tools.perflib.heap.StackFrame;
-import com.android.tools.perflib.heap.StackTrace;
-
-import com.google.common.collect.Lists;
-
-import gnu.trove.TObjectProcedure;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-
-/**
- * A wrapper over the perflib snapshot that provides the behavior we use in
- * ahat.
- */
-class AhatSnapshot {
-  private final Snapshot mSnapshot;
-  private final List<Heap> mHeaps;
-
-  // Map from Instance to the list of Instances it immediately dominates.
-  private final Map<Instance, List<Instance>> mDominated
-    = new HashMap<Instance, List<Instance>>();
-
-  // Collection of objects whose immediate dominator is the SENTINEL_ROOT.
-  private final List<Instance> mRooted = new ArrayList<Instance>();
-
-  // Map from roots to their types.
-  // Instances are only included if they are roots, and the collection of root
-  // types is guaranteed to be non-empty.
-  private final Map<Instance, Collection<RootType>> mRoots
-    = new HashMap<Instance, Collection<RootType>>();
-
-  private final Site mRootSite = new Site("ROOT");
-  private final Map<Heap, Long> mHeapSizes = new HashMap<Heap, Long>();
-
-  private final List<InstanceUtils.NativeAllocation> mNativeAllocations
-    = new ArrayList<InstanceUtils.NativeAllocation>();
-
-  /**
-   * Create an AhatSnapshot from an hprof file.
-   */
-  public static AhatSnapshot fromHprof(File hprof, ProguardMap map) throws IOException {
-    Snapshot snapshot = Snapshot.createSnapshot(new MemoryMappedFileBuffer(hprof), map);
-    snapshot.computeDominators();
-    return new AhatSnapshot(snapshot);
-  }
-
-  /**
-   * Construct an AhatSnapshot for the given perflib snapshot.
-   * Ther user is responsible for calling snapshot.computeDominators before
-   * calling this AhatSnapshot constructor.
-   */
-  private AhatSnapshot(Snapshot snapshot) {
-    mSnapshot = snapshot;
-    mHeaps = new ArrayList<Heap>(mSnapshot.getHeaps());
-
-    final ClassObj javaLangClass = mSnapshot.findClass("java.lang.Class");
-    for (Heap heap : mHeaps) {
-      // Use a single element array for the total to act as a reference to a
-      // long.
-      final long[] total = new long[]{0};
-      TObjectProcedure<Instance> processInstance = new TObjectProcedure<Instance>() {
-        @Override
-        public boolean execute(Instance inst) {
-          Instance dominator = inst.getImmediateDominator();
-          if (dominator != null) {
-            total[0] += inst.getSize();
-
-            if (dominator == Snapshot.SENTINEL_ROOT) {
-              mRooted.add(inst);
-            }
-
-            // Properly label the class of a class object.
-            if (inst instanceof ClassObj && javaLangClass != null && inst.getClassObj() == null) {
-                inst.setClassId(javaLangClass.getId());
-            }
-
-            // Update dominated instances.
-            List<Instance> instances = mDominated.get(dominator);
-            if (instances == null) {
-              instances = new ArrayList<Instance>();
-              mDominated.put(dominator, instances);
-            }
-            instances.add(inst);
-
-            // Update sites.
-            List<StackFrame> path = Collections.emptyList();
-            StackTrace stack = getStack(inst);
-            int stackId = getStackTraceSerialNumber(stack);
-            if (stack != null) {
-              StackFrame[] frames = getStackFrames(stack);
-              if (frames != null && frames.length > 0) {
-                path = Lists.reverse(Arrays.asList(frames));
-              }
-            }
-            mRootSite.add(stackId, 0, path.iterator(), inst);
-
-            // Update native allocations.
-            InstanceUtils.NativeAllocation alloc = InstanceUtils.getNativeAllocation(inst);
-            if (alloc != null) {
-              mNativeAllocations.add(alloc);
-            }
-          }
-          return true;
-        }
-      };
-      for (Instance instance : heap.getClasses()) {
-        processInstance.execute(instance);
-      }
-      heap.forEachInstance(processInstance);
-      mHeapSizes.put(heap, total[0]);
-    }
-
-    // Record the roots and their types.
-    for (RootObj root : snapshot.getGCRoots()) {
-      Instance inst = root.getReferredInstance();
-      Collection<RootType> types = mRoots.get(inst);
-      if (types == null) {
-        types = new HashSet<RootType>();
-        mRoots.put(inst, types);
-      }
-      types.add(root.getRootType());
-    }
-  }
-
-  // Note: This method is exposed for testing purposes.
-  public ClassObj findClass(String name) {
-    return mSnapshot.findClass(name);
-  }
-
-  public Instance findInstance(long id) {
-    return mSnapshot.findInstance(id);
-  }
-
-  public int getHeapIndex(Heap heap) {
-    return mSnapshot.getHeapIndex(heap);
-  }
-
-  public Heap getHeap(String name) {
-    return mSnapshot.getHeap(name);
-  }
-
-  /**
-   * Returns a collection of instances whose immediate dominator is the
-   * SENTINEL_ROOT.
-   */
-  public List<Instance> getRooted() {
-    return mRooted;
-  }
-
-  /**
-   * Returns true if the given instance is a root.
-   */
-  public boolean isRoot(Instance inst) {
-    return mRoots.containsKey(inst);
-  }
-
-  /**
-   * Returns the list of root types for the given instance, or null if the
-   * instance is not a root.
-   */
-  public Collection<RootType> getRootTypes(Instance inst) {
-    return mRoots.get(inst);
-  }
-
-  public List<Heap> getHeaps() {
-    return mHeaps;
-  }
-
-  public Site getRootSite() {
-    return mRootSite;
-  }
-
-  /**
-   * Look up the site at which the given object was allocated.
-   */
-  public Site getSiteForInstance(Instance inst) {
-    Site site = mRootSite;
-    StackTrace stack = getStack(inst);
-    if (stack != null) {
-      StackFrame[] frames = getStackFrames(stack);
-      if (frames != null) {
-        List<StackFrame> path = Lists.reverse(Arrays.asList(frames));
-        site = mRootSite.getChild(path.iterator());
-      }
-    }
-    return site;
-  }
-
-  /**
-   * Return a list of those objects immediately dominated by the given
-   * instance.
-   */
-  public List<Instance> getDominated(Instance inst) {
-    return mDominated.get(inst);
-  }
-
-  /**
-   * Return the total size of reachable objects allocated on the given heap.
-   */
-  public long getHeapSize(Heap heap) {
-    return mHeapSizes.get(heap);
-  }
-
-  /**
-   * Return the class name for the given class object.
-   * classObj may be null, in which case "(class unknown)" is returned.
-   */
-  public static String getClassName(ClassObj classObj) {
-    if (classObj == null) {
-      return "(class unknown)";
-    }
-    return classObj.getClassName();
-  }
-
-  // Return the stack where the given instance was allocated.
-  private static StackTrace getStack(Instance inst) {
-    return inst.getStack();
-  }
-
-  // Return the list of stack frames for a stack trace.
-  private static StackFrame[] getStackFrames(StackTrace stack) {
-    return stack.getFrames();
-  }
-
-  // Return the serial number of the given stack trace.
-  private static int getStackTraceSerialNumber(StackTrace stack) {
-    return stack.getSerialNumber();
-  }
-
-  // Get the site associated with the given stack id and depth.
-  // Returns the root site if no such site found.
-  // depth of -1 means the full stack.
-  public Site getSite(int stackId, int depth) {
-    Site site = mRootSite;
-    StackTrace stack = mSnapshot.getStackTrace(stackId);
-    if (stack != null) {
-      StackFrame[] frames = getStackFrames(stack);
-      if (frames != null) {
-        List<StackFrame> path = Lists.reverse(Arrays.asList(frames));
-        if (depth >= 0) {
-          path = path.subList(0, depth);
-        }
-        site = mRootSite.getChild(path.iterator());
-      }
-    }
-    return site;
-  }
-
-  // Return a list of known native allocations in the snapshot.
-  public List<InstanceUtils.NativeAllocation> getNativeAllocations() {
-    return mNativeAllocations;
-  }
-}
diff --git a/tools/ahat/src/BitmapHandler.java b/tools/ahat/src/BitmapHandler.java
index 0f567e3..836aef6 100644
--- a/tools/ahat/src/BitmapHandler.java
+++ b/tools/ahat/src/BitmapHandler.java
@@ -16,7 +16,8 @@
 
 package com.android.ahat;
 
-import com.android.tools.perflib.heap.Instance;
+import com.android.ahat.heapdump.AhatInstance;
+import com.android.ahat.heapdump.AhatSnapshot;
 import com.sun.net.httpserver.HttpExchange;
 import com.sun.net.httpserver.HttpHandler;
 import java.awt.image.BufferedImage;
@@ -38,9 +39,9 @@
       Query query = new Query(exchange.getRequestURI());
       long id = query.getLong("id", 0);
       BufferedImage bitmap = null;
-      Instance inst = mSnapshot.findInstance(id);
+      AhatInstance inst = mSnapshot.findInstance(id);
       if (inst != null) {
-        bitmap = InstanceUtils.asBitmap(inst);
+        bitmap = inst.asBitmap();
       }
 
       if (bitmap != null) {
diff --git a/tools/ahat/src/Column.java b/tools/ahat/src/Column.java
index b7f2829..819e586 100644
--- a/tools/ahat/src/Column.java
+++ b/tools/ahat/src/Column.java
@@ -22,14 +22,24 @@
 class Column {
   public DocString heading;
   public Align align;
+  public boolean visible;
 
   public static enum Align {
     LEFT, RIGHT
   };
 
-  public Column(DocString heading, Align align) {
+  public Column(DocString heading, Align align, boolean visible) {
     this.heading = heading;
     this.align = align;
+    this.visible = visible;
+  }
+
+  public Column(String heading, Align align, boolean visible) {
+    this(DocString.text(heading), align, visible);
+  }
+
+  public Column(DocString heading, Align align) {
+    this(heading, align, true);
   }
 
   /**
diff --git a/tools/ahat/src/DocString.java b/tools/ahat/src/DocString.java
index 19666de..c6303c8 100644
--- a/tools/ahat/src/DocString.java
+++ b/tools/ahat/src/DocString.java
@@ -53,7 +53,6 @@
   public static DocString link(URI uri, DocString content) {
     DocString doc = new DocString();
     return doc.appendLink(uri, content);
-
   }
 
   /**
@@ -86,6 +85,78 @@
     return this;
   }
 
+  /**
+   * Adorn the given string to indicate it represents something added relative
+   * to a baseline.
+   */
+  public static DocString added(DocString str) {
+    DocString string = new DocString();
+    string.mStringBuilder.append("<span class=\"added\">");
+    string.mStringBuilder.append(str.html());
+    string.mStringBuilder.append("</span>");
+    return string;
+  }
+
+  /**
+   * Adorn the given string to indicate it represents something added relative
+   * to a baseline.
+   */
+  public static DocString added(String str) {
+    return added(text(str));
+  }
+
+  /**
+   * Adorn the given string to indicate it represents something removed relative
+   * to a baseline.
+   */
+  public static DocString removed(DocString str) {
+    DocString string = new DocString();
+    string.mStringBuilder.append("<span class=\"removed\">");
+    string.mStringBuilder.append(str.html());
+    string.mStringBuilder.append("</span>");
+    return string;
+  }
+
+  /**
+   * Adorn the given string to indicate it represents something removed relative
+   * to a baseline.
+   */
+  public static DocString removed(String str) {
+    return removed(text(str));
+  }
+
+  /**
+   * Standard formatted DocString for describing a change in size relative to
+   * a baseline.
+   * @param noCurrent - whether no current object exists.
+   * @param noBaseline - whether no basline object exists.
+   * @param current - the size of the current object.
+   * @param baseline - the size of the baseline object.
+   */
+  public static DocString delta(boolean noCurrent, boolean noBaseline,
+      long current, long baseline) {
+    DocString doc = new DocString();
+    return doc.appendDelta(noCurrent, noBaseline, current, baseline);
+  }
+
+  /**
+   * Standard formatted DocString for describing a change in size relative to
+   * a baseline.
+   */
+  public DocString appendDelta(boolean noCurrent, boolean noBaseline,
+      long current, long baseline) {
+    if (noCurrent) {
+      append(removed(format("%+,14d", 0 - baseline)));
+    } else if (noBaseline) {
+      append(added("new"));
+    } else if (current > baseline) {
+      append(added(format("%+,14d", current - baseline)));
+    } else if (current < baseline) {
+      append(removed(format("%+,14d", current - baseline)));
+    }
+    return this;
+  }
+
   public DocString appendLink(URI uri, DocString content) {
     mStringBuilder.append("<a href=\"");
     mStringBuilder.append(uri.toASCIIString());
diff --git a/tools/ahat/src/DominatedList.java b/tools/ahat/src/DominatedList.java
index 7a673f5..f73e3ca 100644
--- a/tools/ahat/src/DominatedList.java
+++ b/tools/ahat/src/DominatedList.java
@@ -16,8 +16,10 @@
 
 package com.android.ahat;
 
-import com.android.tools.perflib.heap.Heap;
-import com.android.tools.perflib.heap.Instance;
+import com.android.ahat.heapdump.AhatHeap;
+import com.android.ahat.heapdump.AhatInstance;
+import com.android.ahat.heapdump.AhatSnapshot;
+import com.android.ahat.heapdump.Sort;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -39,39 +41,32 @@
    * @param instances the collection of instances to generate a list for
    */
   public static void render(final AhatSnapshot snapshot,
-      Doc doc, Query query, String id, Collection<Instance> instances) {
-    List<Instance> insts = new ArrayList<Instance>(instances);
+      Doc doc, Query query, String id, Collection<AhatInstance> instances) {
+    List<AhatInstance> insts = new ArrayList<AhatInstance>(instances);
     Collections.sort(insts, Sort.defaultInstanceCompare(snapshot));
-    HeapTable.render(doc, query, id, new TableConfig(snapshot), snapshot, insts);
+    HeapTable.render(doc, query, id, new TableConfig(), snapshot, insts);
   }
 
-  private static class TableConfig implements HeapTable.TableConfig<Instance> {
-    AhatSnapshot mSnapshot;
-
-    public TableConfig(AhatSnapshot snapshot) {
-      mSnapshot = snapshot;
-    }
-
+  private static class TableConfig implements HeapTable.TableConfig<AhatInstance> {
     @Override
     public String getHeapsDescription() {
       return "Bytes Retained by Heap";
     }
 
     @Override
-    public long getSize(Instance element, Heap heap) {
-      int index = mSnapshot.getHeapIndex(heap);
-      return element.getRetainedSize(index);
+    public long getSize(AhatInstance element, AhatHeap heap) {
+      return element.getRetainedSize(heap);
     }
 
     @Override
-    public List<HeapTable.ValueConfig<Instance>> getValueConfigs() {
-      HeapTable.ValueConfig<Instance> value = new HeapTable.ValueConfig<Instance>() {
+    public List<HeapTable.ValueConfig<AhatInstance>> getValueConfigs() {
+      HeapTable.ValueConfig<AhatInstance> value = new HeapTable.ValueConfig<AhatInstance>() {
         public String getDescription() {
           return "Object";
         }
 
-        public DocString render(Instance element) {
-          return Value.render(mSnapshot, element);
+        public DocString render(AhatInstance element) {
+          return Summarizer.summarize(element);
         }
       };
       return Collections.singletonList(value);
diff --git a/tools/ahat/src/HeapTable.java b/tools/ahat/src/HeapTable.java
index 5b84048..9abbe4a 100644
--- a/tools/ahat/src/HeapTable.java
+++ b/tools/ahat/src/HeapTable.java
@@ -16,7 +16,9 @@
 
 package com.android.ahat;
 
-import com.android.tools.perflib.heap.Heap;
+import com.android.ahat.heapdump.AhatHeap;
+import com.android.ahat.heapdump.AhatSnapshot;
+import com.android.ahat.heapdump.Diffable;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
@@ -39,21 +41,31 @@
    */
   public interface TableConfig<T> {
     String getHeapsDescription();
-    long getSize(T element, Heap heap);
+    long getSize(T element, AhatHeap heap);
     List<ValueConfig<T>> getValueConfigs();
   }
 
+  private static DocString sizeString(long size, boolean isPlaceHolder) {
+    DocString string = new DocString();
+    if (isPlaceHolder) {
+      string.append(DocString.removed("del"));
+    } else if (size != 0) {
+      string.appendFormat("%,14d", size);
+    }
+    return string;
+  }
+
   /**
    * Render the table to the given document.
    * @param query - The page query.
    * @param id - A unique identifier for the table on the page.
    */
-  public static <T> void render(Doc doc, Query query, String id,
+  public static <T extends Diffable<T>> void render(Doc doc, Query query, String id,
       TableConfig<T> config, AhatSnapshot snapshot, List<T> elements) {
     // Only show the heaps that have non-zero entries.
-    List<Heap> heaps = new ArrayList<Heap>();
-    for (Heap heap : snapshot.getHeaps()) {
-      if (hasNonZeroEntry(snapshot, heap, config, elements)) {
+    List<AhatHeap> heaps = new ArrayList<AhatHeap>();
+    for (AhatHeap heap : snapshot.getHeaps()) {
+      if (hasNonZeroEntry(heap, config, elements)) {
         heaps.add(heap);
       }
     }
@@ -61,14 +73,14 @@
     List<ValueConfig<T>> values = config.getValueConfigs();
 
     // Print the heap and values descriptions.
-    boolean showTotal = heaps.size() > 1;
     List<Column> subcols = new ArrayList<Column>();
-    for (Heap heap : heaps) {
+    for (AhatHeap heap : heaps) {
       subcols.add(new Column(heap.getName(), Column.Align.RIGHT));
+      subcols.add(new Column("Δ", Column.Align.RIGHT, snapshot.isDiffed()));
     }
-    if (showTotal) {
-      subcols.add(new Column("Total", Column.Align.RIGHT));
-    }
+    boolean showTotal = heaps.size() > 1;
+    subcols.add(new Column("Total", Column.Align.RIGHT, showTotal));
+    subcols.add(new Column("Δ", Column.Align.RIGHT, showTotal && snapshot.isDiffed()));
     List<Column> cols = new ArrayList<Column>();
     for (ValueConfig value : values) {
       cols.add(new Column(value.getDescription()));
@@ -79,16 +91,20 @@
     SubsetSelector<T> selector = new SubsetSelector(query, id, elements);
     ArrayList<DocString> vals = new ArrayList<DocString>();
     for (T elem : selector.selected()) {
+      T base = elem.getBaseline();
       vals.clear();
       long total = 0;
-      for (Heap heap : heaps) {
+      long basetotal = 0;
+      for (AhatHeap heap : heaps) {
         long size = config.getSize(elem, heap);
+        long basesize = config.getSize(base, heap.getBaseline());
         total += size;
-        vals.add(size == 0 ? DocString.text("") : DocString.format("%,14d", size));
+        basetotal += basesize;
+        vals.add(sizeString(size, elem.isPlaceHolder()));
+        vals.add(DocString.delta(elem.isPlaceHolder(), base.isPlaceHolder(), size, basesize));
       }
-      if (showTotal) {
-        vals.add(total == 0 ? DocString.text("") : DocString.format("%,14d", total));
-      }
+      vals.add(sizeString(total, elem.isPlaceHolder()));
+      vals.add(DocString.delta(elem.isPlaceHolder(), base.isPlaceHolder(), total, basetotal));
 
       for (ValueConfig<T> value : values) {
         vals.add(value.render(elem));
@@ -99,27 +115,36 @@
     // Print a summary of the remaining entries if there are any.
     List<T> remaining = selector.remaining();
     if (!remaining.isEmpty()) {
-      Map<Heap, Long> summary = new HashMap<Heap, Long>();
-      for (Heap heap : heaps) {
+      Map<AhatHeap, Long> summary = new HashMap<AhatHeap, Long>();
+      Map<AhatHeap, Long> basesummary = new HashMap<AhatHeap, Long>();
+      for (AhatHeap heap : heaps) {
         summary.put(heap, 0L);
+        basesummary.put(heap, 0L);
       }
 
       for (T elem : remaining) {
-        for (Heap heap : heaps) {
-          summary.put(heap, summary.get(heap) + config.getSize(elem, heap));
+        for (AhatHeap heap : heaps) {
+          long size = config.getSize(elem, heap);
+          summary.put(heap, summary.get(heap) + size);
+
+          long basesize = config.getSize(elem.getBaseline(), heap.getBaseline());
+          basesummary.put(heap, basesummary.get(heap) + basesize);
         }
       }
 
       vals.clear();
       long total = 0;
-      for (Heap heap : heaps) {
+      long basetotal = 0;
+      for (AhatHeap heap : heaps) {
         long size = summary.get(heap);
+        long basesize = basesummary.get(heap);
         total += size;
-        vals.add(DocString.format("%,14d", size));
+        basetotal += basesize;
+        vals.add(sizeString(size, false));
+        vals.add(DocString.delta(false, false, size, basesize));
       }
-      if (showTotal) {
-        vals.add(DocString.format("%,14d", total));
-      }
+      vals.add(sizeString(total, false));
+      vals.add(DocString.delta(false, false, total, basetotal));
 
       for (ValueConfig<T> value : values) {
         vals.add(DocString.text("..."));
@@ -131,11 +156,13 @@
   }
 
   // Returns true if the given heap has a non-zero size entry.
-  public static <T> boolean hasNonZeroEntry(AhatSnapshot snapshot, Heap heap,
+  public static <T extends Diffable<T>> boolean hasNonZeroEntry(AhatHeap heap,
       TableConfig<T> config, List<T> elements) {
-    if (snapshot.getHeapSize(heap) > 0) {
+    AhatHeap baseheap = heap.getBaseline();
+    if (heap.getSize() > 0 || baseheap.getSize() > 0) {
       for (T element : elements) {
-        if (config.getSize(element, heap) > 0) {
+        if (config.getSize(element, heap) > 0 ||
+            config.getSize(element.getBaseline(), baseheap) > 0) {
           return true;
         }
       }
diff --git a/tools/ahat/src/HelpHandler.java b/tools/ahat/src/HelpHandler.java
deleted file mode 100644
index 8de3c85..0000000
--- a/tools/ahat/src/HelpHandler.java
+++ /dev/null
@@ -1,52 +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.
- */
-
-package com.android.ahat;
-
-import com.google.common.io.ByteStreams;
-import com.sun.net.httpserver.HttpExchange;
-import com.sun.net.httpserver.HttpHandler;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.PrintStream;
-
-/**
- * HelpHandler.
- *
- * HttpHandler to show the help page.
- */
-class HelpHandler implements HttpHandler {
-
-  @Override
-  public void handle(HttpExchange exchange) throws IOException {
-    ClassLoader loader = HelpHandler.class.getClassLoader();
-    exchange.getResponseHeaders().add("Content-Type", "text/html;charset=utf-8");
-    exchange.sendResponseHeaders(200, 0);
-    PrintStream ps = new PrintStream(exchange.getResponseBody());
-    HtmlDoc doc = new HtmlDoc(ps, DocString.text("ahat"), DocString.uri("style.css"));
-    doc.menu(Menu.getMenu());
-
-    InputStream is = loader.getResourceAsStream("help.html");
-    if (is == null) {
-      ps.println("No help available.");
-    } else {
-      ByteStreams.copy(is, ps);
-    }
-
-    doc.close();
-    ps.close();
-  }
-}
diff --git a/tools/ahat/src/HtmlDoc.java b/tools/ahat/src/HtmlDoc.java
index 5ccbacb..5a22fc7 100644
--- a/tools/ahat/src/HtmlDoc.java
+++ b/tools/ahat/src/HtmlDoc.java
@@ -86,19 +86,27 @@
     mCurrentTableColumns = columns;
     ps.println("<table>");
     for (int i = 0; i < columns.length - 1; i++) {
-      ps.format("<th>%s</th>", columns[i].heading.html());
+      if (columns[i].visible) {
+        ps.format("<th>%s</th>", columns[i].heading.html());
+      }
     }
 
     // Align the last header to the left so it's easier to see if the last
     // column is very wide.
-    ps.format("<th align=\"left\">%s</th>", columns[columns.length - 1].heading.html());
+    if (columns[columns.length - 1].visible) {
+      ps.format("<th align=\"left\">%s</th>", columns[columns.length - 1].heading.html());
+    }
   }
 
   @Override
   public void table(DocString description, List<Column> subcols, List<Column> cols) {
     mCurrentTableColumns = new Column[subcols.size() + cols.size()];
     int j = 0;
+    int visibleSubCols = 0;
     for (Column col : subcols) {
+      if (col.visible) {
+        visibleSubCols++;
+      }
       mCurrentTableColumns[j] = col;
       j++;
     }
@@ -108,21 +116,27 @@
     }
 
     ps.println("<table>");
-    ps.format("<tr><th colspan=\"%d\">%s</th>", subcols.size(), description.html());
+    ps.format("<tr><th colspan=\"%d\">%s</th>", visibleSubCols, description.html());
     for (int i = 0; i < cols.size() - 1; i++) {
-      ps.format("<th rowspan=\"2\">%s</th>", cols.get(i).heading.html());
+      if (cols.get(i).visible) {
+        ps.format("<th rowspan=\"2\">%s</th>", cols.get(i).heading.html());
+      }
     }
     if (!cols.isEmpty()) {
       // Align the last column header to the left so it can still be seen if
       // the last column is very wide.
-      ps.format("<th align=\"left\" rowspan=\"2\">%s</th>",
-          cols.get(cols.size() - 1).heading.html());
+      Column col = cols.get(cols.size() - 1);
+      if (col.visible) {
+        ps.format("<th align=\"left\" rowspan=\"2\">%s</th>", col.heading.html());
+      }
     }
     ps.println("</tr>");
 
     ps.print("<tr>");
     for (Column subcol : subcols) {
-      ps.format("<th>%s</th>", subcol.heading.html());
+      if (subcol.visible) {
+        ps.format("<th>%s</th>", subcol.heading.html());
+      }
     }
     ps.println("</tr>");
   }
@@ -141,11 +155,13 @@
 
     ps.print("<tr>");
     for (int i = 0; i < values.length; i++) {
+      if (mCurrentTableColumns[i].visible) {
       ps.print("<td");
-      if (mCurrentTableColumns[i].align == Column.Align.RIGHT) {
-        ps.print(" align=\"right\"");
+        if (mCurrentTableColumns[i].align == Column.Align.RIGHT) {
+          ps.print(" align=\"right\"");
+        }
+        ps.format(">%s</td>", values[i].html());
       }
-      ps.format(">%s</td>", values[i].html());
     }
     ps.println("</tr>");
   }
diff --git a/tools/ahat/src/InstanceUtils.java b/tools/ahat/src/InstanceUtils.java
deleted file mode 100644
index a062afd..0000000
--- a/tools/ahat/src/InstanceUtils.java
+++ /dev/null
@@ -1,457 +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.
- */
-
-package com.android.ahat;
-
-import com.android.tools.perflib.heap.ArrayInstance;
-import com.android.tools.perflib.heap.ClassInstance;
-import com.android.tools.perflib.heap.ClassObj;
-import com.android.tools.perflib.heap.Field;
-import com.android.tools.perflib.heap.Heap;
-import com.android.tools.perflib.heap.Instance;
-import com.android.tools.perflib.heap.RootObj;
-import com.android.tools.perflib.heap.Type;
-
-import java.awt.image.BufferedImage;
-import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Utilities for extracting information from hprof instances.
- */
-class InstanceUtils {
-  /**
-   * Returns true if the given instance is an instance of a class with the
-   * given name.
-   */
-  private static boolean isInstanceOfClass(Instance inst, String className) {
-    ClassObj cls = (inst == null) ? null : inst.getClassObj();
-    return (cls != null && className.equals(cls.getClassName()));
-  }
-
-  /**
-   * Read the byte[] value from an hprof Instance.
-   * Returns null if the instance is not a byte array.
-   */
-  private static byte[] asByteArray(Instance inst) {
-    if (!(inst instanceof ArrayInstance)) {
-      return null;
-    }
-
-    ArrayInstance array = (ArrayInstance) inst;
-    if (array.getArrayType() != Type.BYTE) {
-      return null;
-    }
-
-    Object[] objs = array.getValues();
-    byte[] bytes = new byte[objs.length];
-    for (int i = 0; i < objs.length; i++) {
-      Byte b = (Byte) objs[i];
-      bytes[i] = b.byteValue();
-    }
-    return bytes;
-  }
-
-
-  /**
-   * Read the string value from an hprof Instance.
-   * Returns null if the object can't be interpreted as a string.
-   */
-  public static String asString(Instance inst) {
-    return asString(inst, -1);
-  }
-
-  /**
-   * Read the string value from an hprof Instance.
-   * Returns null if the object can't be interpreted as a string.
-   * The returned string is truncated to maxChars characters.
-   * If maxChars is negative, the returned string is not truncated.
-   */
-  public static String asString(Instance inst, int maxChars) {
-    // The inst object could either be a java.lang.String or a char[]. If it
-    // is a char[], use that directly as the value, otherwise use the value
-    // field of the string object. The field accesses for count and offset
-    // later on will work okay regardless of what type the inst object is.
-    boolean isString = isInstanceOfClass(inst, "java.lang.String");
-    Object value = isString ? getField(inst, "value") : inst;
-
-    if (!(value instanceof ArrayInstance)) {
-      return null;
-    }
-
-    ArrayInstance chars = (ArrayInstance) value;
-    int numChars = chars.getLength();
-    int offset = getIntField(inst, "offset", 0);
-    int count = getIntField(inst, "count", numChars);
-
-    // With string compression enabled, the array type can be BYTE but in that case
-    // offset must be 0 and count must match numChars.
-    if (isString && (chars.getArrayType() == Type.BYTE) && (offset == 0) && (count == numChars)) {
-      int length = (0 <= maxChars && maxChars < numChars) ? maxChars : numChars;
-      return new String(chars.asRawByteArray(/* offset */ 0, length), StandardCharsets.US_ASCII);
-    }
-    if (chars.getArrayType() != Type.CHAR) {
-      return null;
-    }
-    if (count == 0) {
-      return "";
-    }
-    if (0 <= maxChars && maxChars < count) {
-      count = maxChars;
-    }
-
-    int end = offset + count - 1;
-    if (offset >= 0 && offset < numChars && end >= 0 && end < numChars) {
-      return new String(chars.asCharArray(offset, count));
-    }
-    return null;
-  }
-
-  /**
-   * Read the bitmap data for the given android.graphics.Bitmap object.
-   * Returns null if the object isn't for android.graphics.Bitmap or the
-   * bitmap data couldn't be read.
-   */
-  public static BufferedImage asBitmap(Instance inst) {
-    if (!isInstanceOfClass(inst, "android.graphics.Bitmap")) {
-      return null;
-    }
-
-    Integer width = getIntField(inst, "mWidth", null);
-    if (width == null) {
-      return null;
-    }
-
-    Integer height = getIntField(inst, "mHeight", null);
-    if (height == null) {
-      return null;
-    }
-
-    byte[] buffer = getByteArrayField(inst, "mBuffer");
-    if (buffer == null) {
-      return null;
-    }
-
-    // Convert the raw data to an image
-    // Convert BGRA to ABGR
-    int[] abgr = new int[height * width];
-    for (int i = 0; i < abgr.length; i++) {
-      abgr[i] = (
-          (((int) buffer[i * 4 + 3] & 0xFF) << 24)
-          + (((int) buffer[i * 4 + 0] & 0xFF) << 16)
-          + (((int) buffer[i * 4 + 1] & 0xFF) << 8)
-          + ((int) buffer[i * 4 + 2] & 0xFF));
-    }
-
-    BufferedImage bitmap = new BufferedImage(
-        width, height, BufferedImage.TYPE_4BYTE_ABGR);
-    bitmap.setRGB(0, 0, width, height, abgr, 0, width);
-    return bitmap;
-  }
-
-  /**
-   * Read a field of an instance.
-   * Returns null if the field value is null or if the field couldn't be read.
-   */
-  public static Object getField(Instance inst, String fieldName) {
-    if (!(inst instanceof ClassInstance)) {
-      return null;
-    }
-
-    ClassInstance clsinst = (ClassInstance) inst;
-    Object value = null;
-    int count = 0;
-    for (ClassInstance.FieldValue field : clsinst.getValues()) {
-      if (fieldName.equals(field.getField().getName())) {
-        value = field.getValue();
-        count++;
-      }
-    }
-    return count == 1 ? value : null;
-  }
-
-  /**
-   * Read a reference field of an instance.
-   * Returns null if the field value is null, or if the field couldn't be read.
-   */
-  public static Instance getRefField(Instance inst, String fieldName) {
-    Object value = getField(inst, fieldName);
-    if (!(value instanceof Instance)) {
-      return null;
-    }
-    return (Instance) value;
-  }
-
-  /**
-   * Read an int field of an instance.
-   * The field is assumed to be an int type.
-   * Returns <code>def</code> if the field value is not an int or could not be
-   * read.
-   */
-  private static Integer getIntField(Instance inst, String fieldName, Integer def) {
-    Object value = getField(inst, fieldName);
-    if (!(value instanceof Integer)) {
-      return def;
-    }
-    return (Integer) value;
-  }
-
-  /**
-   * Read a long field of an instance.
-   * The field is assumed to be a long type.
-   * Returns <code>def</code> if the field value is not an long or could not
-   * be read.
-   */
-  private static Long getLongField(Instance inst, String fieldName, Long def) {
-    Object value = getField(inst, fieldName);
-    if (!(value instanceof Long)) {
-      return def;
-    }
-    return (Long) value;
-  }
-
-  /**
-   * Read the given field from the given instance.
-   * The field is assumed to be a byte[] field.
-   * Returns null if the field value is null, not a byte[] or could not be read.
-   */
-  private static byte[] getByteArrayField(Instance inst, String fieldName) {
-    Object value = getField(inst, fieldName);
-    if (!(value instanceof Instance)) {
-      return null;
-    }
-    return asByteArray((Instance) value);
-  }
-
-  // Return the bitmap instance associated with this object, or null if there
-  // is none. This works for android.graphics.Bitmap instances and their
-  // underlying Byte[] instances.
-  public static Instance getAssociatedBitmapInstance(Instance inst) {
-    ClassObj cls = inst.getClassObj();
-    if (cls == null) {
-      return null;
-    }
-
-    if ("android.graphics.Bitmap".equals(cls.getClassName())) {
-      return inst;
-    }
-
-    if (inst instanceof ArrayInstance) {
-      ArrayInstance array = (ArrayInstance) inst;
-      if (array.getArrayType() == Type.BYTE && inst.getHardReverseReferences().size() == 1) {
-        Instance ref = inst.getHardReverseReferences().get(0);
-        ClassObj clsref = ref.getClassObj();
-        if (clsref != null && "android.graphics.Bitmap".equals(clsref.getClassName())) {
-          return ref;
-        }
-      }
-    }
-    return null;
-  }
-
-  private static boolean isJavaLangRefReference(Instance inst) {
-    ClassObj cls = (inst == null) ? null : inst.getClassObj();
-    while (cls != null) {
-      if ("java.lang.ref.Reference".equals(cls.getClassName())) {
-        return true;
-      }
-      cls = cls.getSuperClassObj();
-    }
-    return false;
-  }
-
-  public static Instance getReferent(Instance inst) {
-    if (isJavaLangRefReference(inst)) {
-      return getRefField(inst, "referent");
-    }
-    return null;
-  }
-
-  /**
-   * Assuming inst represents a DexCache object, return the dex location for
-   * that dex cache. Returns null if the given instance doesn't represent a
-   * DexCache object or the location could not be found.
-   * If maxChars is non-negative, the returned location is truncated to
-   * maxChars in length.
-   */
-  public static String getDexCacheLocation(Instance inst, int maxChars) {
-    if (isInstanceOfClass(inst, "java.lang.DexCache")) {
-      Instance location = getRefField(inst, "location");
-      if (location != null) {
-        return asString(location, maxChars);
-      }
-    }
-    return null;
-  }
-
-  public static class NativeAllocation {
-    public long size;
-    public Heap heap;
-    public long pointer;
-    public Instance referent;
-
-    public NativeAllocation(long size, Heap heap, long pointer, Instance referent) {
-      this.size = size;
-      this.heap = heap;
-      this.pointer = pointer;
-      this.referent = referent;
-    }
-  }
-
-  /**
-   * Assuming inst represents a NativeAllocation, return information about the
-   * native allocation. Returns null if the given instance doesn't represent a
-   * native allocation.
-   */
-  public static NativeAllocation getNativeAllocation(Instance inst) {
-    if (!isInstanceOfClass(inst, "libcore.util.NativeAllocationRegistry$CleanerThunk")) {
-      return null;
-    }
-
-    Long pointer = InstanceUtils.getLongField(inst, "nativePtr", null);
-    if (pointer == null) {
-      return null;
-    }
-
-    // Search for the registry field of inst.
-    // Note: We know inst as an instance of ClassInstance because we already
-    // read the nativePtr field from it.
-    Instance registry = null;
-    for (ClassInstance.FieldValue field : ((ClassInstance) inst).getValues()) {
-      Object fieldValue = field.getValue();
-      if (fieldValue instanceof Instance) {
-        Instance fieldInst = (Instance) fieldValue;
-        if (isInstanceOfClass(fieldInst, "libcore.util.NativeAllocationRegistry")) {
-          registry = fieldInst;
-          break;
-        }
-      }
-    }
-
-    if (registry == null) {
-      return null;
-    }
-
-    Long size = InstanceUtils.getLongField(registry, "size", null);
-    if (size == null) {
-      return null;
-    }
-
-    Instance referent = null;
-    for (Instance ref : inst.getHardReverseReferences()) {
-      if (isInstanceOfClass(ref, "sun.misc.Cleaner")) {
-        referent = InstanceUtils.getReferent(ref);
-        if (referent != null) {
-          break;
-        }
-      }
-    }
-
-    if (referent == null) {
-      return null;
-    }
-    return new NativeAllocation(size, inst.getHeap(), pointer, referent);
-  }
-
-  public static class PathElement {
-    public final Instance instance;
-    public final String field;
-    public boolean isDominator;
-
-    public PathElement(Instance instance, String field) {
-      this.instance = instance;
-      this.field = field;
-      this.isDominator = false;
-    }
-  }
-
-  /**
-   * Returns a sample path from a GC root to this instance.
-   * The given instance is included as the last element of the path with an
-   * empty field description.
-   */
-  public static List<PathElement> getPathFromGcRoot(Instance inst) {
-    List<PathElement> path = new ArrayList<PathElement>();
-
-    Instance dom = inst;
-    for (PathElement elem = new PathElement(inst, ""); elem != null;
-        elem = getNextPathElementToGcRoot(elem.instance)) {
-      if (elem.instance == dom) {
-        elem.isDominator = true;
-        dom = dom.getImmediateDominator();
-      }
-      path.add(elem);
-    }
-    Collections.reverse(path);
-    return path;
-  }
-
-  /**
-   * Returns the next instance to GC root from this object and a string
-   * description of which field of that object refers to the given instance.
-   * Returns null if the given instance has no next instance to the gc root.
-   */
-  private static PathElement getNextPathElementToGcRoot(Instance inst) {
-    Instance parent = inst.getNextInstanceToGcRoot();
-    if (parent == null || parent instanceof RootObj) {
-      return null;
-    }
-
-    // Search the parent for the reference to the child.
-    // TODO: This seems terribly inefficient. Can we use data structures to
-    // help us here?
-    String description = ".???";
-    if (parent instanceof ArrayInstance) {
-      ArrayInstance array = (ArrayInstance)parent;
-      Object[] values = array.getValues();
-      for (int i = 0; i < values.length; i++) {
-        if (values[i] instanceof Instance) {
-          Instance ref = (Instance)values[i];
-          if (ref.getId() == inst.getId()) {
-            description = String.format("[%d]", i);
-            break;
-          }
-        }
-      }
-    } else if (parent instanceof ClassObj) {
-      ClassObj cls = (ClassObj)parent;
-      for (Map.Entry<Field, Object> entries : cls.getStaticFieldValues().entrySet()) {
-        if (entries.getValue() instanceof Instance) {
-          Instance ref = (Instance)entries.getValue();
-          if (ref.getId() == inst.getId()) {
-            description = "." + entries.getKey().getName();
-            break;
-          }
-        }
-      }
-    } else if (parent instanceof ClassInstance) {
-      ClassInstance obj = (ClassInstance)parent;
-      for (ClassInstance.FieldValue fields : obj.getValues()) {
-        if (fields.getValue() instanceof Instance) {
-          Instance ref = (Instance)fields.getValue();
-          if (ref.getId() == inst.getId()) {
-            description = "." + fields.getField().getName();
-            break;
-          }
-        }
-      }
-    }
-    return new PathElement(parent, description);
-  }
-}
diff --git a/tools/ahat/src/Main.java b/tools/ahat/src/Main.java
index c79b578..b8552fe 100644
--- a/tools/ahat/src/Main.java
+++ b/tools/ahat/src/Main.java
@@ -16,6 +16,8 @@
 
 package com.android.ahat;
 
+import com.android.ahat.heapdump.AhatSnapshot;
+import com.android.ahat.heapdump.Diff;
 import com.android.tools.perflib.heap.ProguardMap;
 import com.sun.net.httpserver.HttpServer;
 import java.io.File;
@@ -29,15 +31,18 @@
 public class Main {
 
   public static void help(PrintStream out) {
-    out.println("java -jar ahat.jar [-p port] [--proguard-map FILE] FILE");
-    out.println("  Launch an http server for viewing "
-        + "the given Android heap-dump FILE.");
+    out.println("java -jar ahat.jar [OPTIONS] FILE");
+    out.println("  Launch an http server for viewing the given Android heap dump FILE.");
     out.println("");
-    out.println("Options:");
+    out.println("OPTIONS:");
     out.println("  -p <port>");
     out.println("     Serve pages on the given port. Defaults to 7100.");
     out.println("  --proguard-map FILE");
     out.println("     Use the proguard map FILE to deobfuscate the heap dump.");
+    out.println("  --baseline FILE");
+    out.println("     Diff the heap dump against the given baseline heap dump FILE.");
+    out.println("  --baseline-proguard-map FILE");
+    out.println("     Use the proguard map FILE to deobfuscate the baseline heap dump.");
     out.println("");
   }
 
@@ -51,7 +56,9 @@
     }
 
     File hprof = null;
+    File hprofbase = null;
     ProguardMap map = new ProguardMap();
+    ProguardMap mapbase = new ProguardMap();
     for (int i = 0; i < args.length; i++) {
       if ("-p".equals(args[i]) && i + 1 < args.length) {
         i++;
@@ -64,6 +71,22 @@
           System.out.println("Unable to read proguard map: " + ex);
           System.out.println("The proguard map will not be used.");
         }
+      } else if ("--baseline-proguard-map".equals(args[i]) && i + 1 < args.length) {
+        i++;
+        try {
+          mapbase.readFromFile(new File(args[i]));
+        } catch (IOException|ParseException ex) {
+          System.out.println("Unable to read baselline proguard map: " + ex);
+          System.out.println("The proguard map will not be used.");
+        }
+      } else if ("--baseline".equals(args[i]) && i + 1 < args.length) {
+        i++;
+        if (hprofbase != null) {
+          System.err.println("multiple baseline heap dumps.");
+          help(System.err);
+          return;
+        }
+        hprofbase = new File(args[i]);
       } else {
         if (hprof != null) {
           System.err.println("multiple input files.");
@@ -88,17 +111,25 @@
 
     System.out.println("Processing hprof file...");
     AhatSnapshot ahat = AhatSnapshot.fromHprof(hprof, map);
-    server.createContext("/", new AhatHttpHandler(new OverviewHandler(ahat, hprof)));
+
+    if (hprofbase != null) {
+      System.out.println("Processing baseline hprof file...");
+      AhatSnapshot base = AhatSnapshot.fromHprof(hprofbase, mapbase);
+
+      System.out.println("Diffing hprof files...");
+      Diff.snapshots(ahat, base);
+    }
+
+    server.createContext("/", new AhatHttpHandler(new OverviewHandler(ahat, hprof, hprofbase)));
     server.createContext("/rooted", new AhatHttpHandler(new RootedHandler(ahat)));
     server.createContext("/object", new AhatHttpHandler(new ObjectHandler(ahat)));
     server.createContext("/objects", new AhatHttpHandler(new ObjectsHandler(ahat)));
     server.createContext("/site", new AhatHttpHandler(new SiteHandler(ahat)));
-    server.createContext("/native", new AhatHttpHandler(new NativeAllocationsHandler(ahat)));
     server.createContext("/bitmap", new BitmapHandler(ahat));
-    server.createContext("/help", new HelpHandler());
     server.createContext("/style.css", new StaticHandler("style.css", "text/css"));
     server.setExecutor(Executors.newFixedThreadPool(1));
     System.out.println("Server started on localhost:" + port);
+
     server.start();
   }
 }
diff --git a/tools/ahat/src/Menu.java b/tools/ahat/src/Menu.java
index 232b849..6d38dc5 100644
--- a/tools/ahat/src/Menu.java
+++ b/tools/ahat/src/Menu.java
@@ -25,11 +25,7 @@
       .append(" - ")
       .appendLink(DocString.uri("rooted"), DocString.text("rooted"))
       .append(" - ")
-      .appendLink(DocString.uri("sites"), DocString.text("allocations"))
-      .append(" - ")
-      .appendLink(DocString.uri("native"), DocString.text("native"))
-      .append(" - ")
-      .appendLink(DocString.uri("help"), DocString.text("help"));
+      .appendLink(DocString.uri("sites"), DocString.text("allocations"));
 
   /**
    * Returns the menu as a DocString.
diff --git a/tools/ahat/src/NativeAllocationsHandler.java b/tools/ahat/src/NativeAllocationsHandler.java
deleted file mode 100644
index 17407e1..0000000
--- a/tools/ahat/src/NativeAllocationsHandler.java
+++ /dev/null
@@ -1,95 +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.
- */
-
-package com.android.ahat;
-
-import java.io.IOException;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
-
-class NativeAllocationsHandler implements AhatHandler {
-  private static final String ALLOCATIONS_ID = "allocations";
-
-  private AhatSnapshot mSnapshot;
-
-  public NativeAllocationsHandler(AhatSnapshot snapshot) {
-    mSnapshot = snapshot;
-  }
-
-  @Override
-  public void handle(Doc doc, Query query) throws IOException {
-    List<InstanceUtils.NativeAllocation> allocs = mSnapshot.getNativeAllocations();
-
-    doc.title("Registered Native Allocations");
-
-    doc.section("Overview");
-    long totalSize = 0;
-    for (InstanceUtils.NativeAllocation alloc : allocs) {
-      totalSize += alloc.size;
-    }
-    doc.descriptions();
-    doc.description(DocString.text("Number of Registered Native Allocations"),
-        DocString.format("%,14d", allocs.size()));
-    doc.description(DocString.text("Total Size of Registered Native Allocations"),
-        DocString.format("%,14d", totalSize));
-    doc.end();
-
-    doc.section("List of Allocations");
-    if (allocs.isEmpty()) {
-      doc.println(DocString.text("(none)"));
-    } else {
-      doc.table(
-          new Column("Size", Column.Align.RIGHT),
-          new Column("Heap"),
-          new Column("Native Pointer"),
-          new Column("Referent"));
-      Comparator<InstanceUtils.NativeAllocation> compare
-        = new Sort.WithPriority<InstanceUtils.NativeAllocation>(
-            new Sort.NativeAllocationByHeapName(),
-            new Sort.NativeAllocationBySize());
-      Collections.sort(allocs, compare);
-      SubsetSelector<InstanceUtils.NativeAllocation> selector
-        = new SubsetSelector(query, ALLOCATIONS_ID, allocs);
-      for (InstanceUtils.NativeAllocation alloc : selector.selected()) {
-        doc.row(
-            DocString.format("%,14d", alloc.size),
-            DocString.text(alloc.heap.getName()),
-            DocString.format("0x%x", alloc.pointer),
-            Value.render(mSnapshot, alloc.referent));
-      }
-
-      // Print a summary of the remaining entries if there are any.
-      List<InstanceUtils.NativeAllocation> remaining = selector.remaining();
-      if (!remaining.isEmpty()) {
-        long total = 0;
-        for (InstanceUtils.NativeAllocation alloc : remaining) {
-          total += alloc.size;
-        }
-
-        doc.row(
-            DocString.format("%,14d", total),
-            DocString.text("..."),
-            DocString.text("..."),
-            DocString.text("..."));
-      }
-
-      doc.end();
-      selector.render(doc);
-    }
-  }
-}
-
diff --git a/tools/ahat/src/ObjectHandler.java b/tools/ahat/src/ObjectHandler.java
index 78aac17..2e0ae6e 100644
--- a/tools/ahat/src/ObjectHandler.java
+++ b/tools/ahat/src/ObjectHandler.java
@@ -16,22 +16,23 @@
 
 package com.android.ahat;
 
-import com.android.tools.perflib.heap.ArrayInstance;
-import com.android.tools.perflib.heap.ClassInstance;
-import com.android.tools.perflib.heap.ClassObj;
-import com.android.tools.perflib.heap.Field;
-import com.android.tools.perflib.heap.Heap;
-import com.android.tools.perflib.heap.Instance;
-import com.android.tools.perflib.heap.RootType;
+import com.android.ahat.heapdump.AhatArrayInstance;
+import com.android.ahat.heapdump.AhatClassInstance;
+import com.android.ahat.heapdump.AhatClassObj;
+import com.android.ahat.heapdump.AhatHeap;
+import com.android.ahat.heapdump.AhatInstance;
+import com.android.ahat.heapdump.AhatSnapshot;
+import com.android.ahat.heapdump.Diff;
+import com.android.ahat.heapdump.FieldValue;
+import com.android.ahat.heapdump.PathElement;
+import com.android.ahat.heapdump.Site;
+import com.android.ahat.heapdump.Value;
 import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
-import java.util.Map;
+import java.util.Objects;
 
-import static com.android.ahat.InstanceUtils.PathElement;
 
 class ObjectHandler implements AhatHandler {
 
@@ -53,35 +54,43 @@
   @Override
   public void handle(Doc doc, Query query) throws IOException {
     long id = query.getLong("id", 0);
-    Instance inst = mSnapshot.findInstance(id);
+    AhatInstance inst = mSnapshot.findInstance(id);
     if (inst == null) {
       doc.println(DocString.format("No object with id %08xl", id));
       return;
     }
+    AhatInstance base = inst.getBaseline();
 
-    doc.title("Object %08x", inst.getUniqueId());
-    doc.big(Value.render(mSnapshot, inst));
+    doc.title("Object %08x", inst.getId());
+    doc.big(Summarizer.summarize(inst));
 
     printAllocationSite(doc, query, inst);
     printGcRootPath(doc, query, inst);
 
     doc.section("Object Info");
-    ClassObj cls = inst.getClassObj();
+    AhatClassObj cls = inst.getClassObj();
     doc.descriptions();
-    doc.description(DocString.text("Class"), Value.render(mSnapshot, cls));
-    doc.description(DocString.text("Size"), DocString.format("%d", inst.getSize()));
-    doc.description(
-        DocString.text("Retained Size"),
-        DocString.format("%d", inst.getTotalRetainedSize()));
+    doc.description(DocString.text("Class"), Summarizer.summarize(cls));
+
+    DocString sizeDescription = DocString.format("%,14d ", inst.getSize());
+    sizeDescription.appendDelta(false, base.isPlaceHolder(),
+        inst.getSize(), base.getSize());
+    doc.description(DocString.text("Size"), sizeDescription);
+
+    DocString rsizeDescription = DocString.format("%,14d ", inst.getTotalRetainedSize());
+    rsizeDescription.appendDelta(false, base.isPlaceHolder(),
+        inst.getTotalRetainedSize(), base.getTotalRetainedSize());
+    doc.description(DocString.text("Retained Size"), rsizeDescription);
+
     doc.description(DocString.text("Heap"), DocString.text(inst.getHeap().getName()));
 
-    Collection<RootType> rootTypes = mSnapshot.getRootTypes(inst);
+    Collection<String> rootTypes = inst.getRootTypes();
     if (rootTypes != null) {
       DocString types = new DocString();
       String comma = "";
-      for (RootType type : rootTypes) {
+      for (String type : rootTypes) {
         types.append(comma);
-        types.append(type.getName());
+        types.append(type);
         comma = ", ";
       }
       doc.description(DocString.text("Root Types"), types);
@@ -90,112 +99,146 @@
     doc.end();
 
     printBitmap(doc, inst);
-    if (inst instanceof ClassInstance) {
-      printClassInstanceFields(doc, query, mSnapshot, (ClassInstance)inst);
-    } else if (inst instanceof ArrayInstance) {
-      printArrayElements(doc, query, mSnapshot, (ArrayInstance)inst);
-    } else if (inst instanceof ClassObj) {
-      printClassInfo(doc, query, mSnapshot, (ClassObj)inst);
+    if (inst.isClassInstance()) {
+      printClassInstanceFields(doc, query, inst.asClassInstance());
+    } else if (inst.isArrayInstance()) {
+      printArrayElements(doc, query, inst.asArrayInstance());
+    } else if (inst.isClassObj()) {
+      printClassInfo(doc, query, inst.asClassObj());
     }
-    printReferences(doc, query, mSnapshot, inst);
+    printReferences(doc, query, inst);
     printDominatedObjects(doc, query, inst);
   }
 
-  private static void printClassInstanceFields(
-      Doc doc, Query query, AhatSnapshot snapshot, ClassInstance inst) {
+  private static void printClassInstanceFields(Doc doc, Query query, AhatClassInstance inst) {
     doc.section("Fields");
-    doc.table(new Column("Type"), new Column("Name"), new Column("Value"));
-    SubsetSelector<ClassInstance.FieldValue> selector
-      = new SubsetSelector(query, INSTANCE_FIELDS_ID, inst.getValues());
-    for (ClassInstance.FieldValue field : selector.selected()) {
-      doc.row(
-          DocString.text(field.getField().getType().toString()),
-          DocString.text(field.getField().getName()),
-          Value.render(snapshot, field.getValue()));
+    AhatInstance base = inst.getBaseline();
+    List<FieldValue> fields = inst.getInstanceFields();
+    if (!base.isPlaceHolder()) {
+      Diff.fields(fields, base.asClassInstance().getInstanceFields());
     }
-    doc.end();
+    SubsetSelector<FieldValue> selector = new SubsetSelector(query, INSTANCE_FIELDS_ID, fields);
+    printFields(doc, inst != base && !base.isPlaceHolder(), selector.selected());
     selector.render(doc);
   }
 
-  private static void printArrayElements(
-      Doc doc, Query query, AhatSnapshot snapshot, ArrayInstance array) {
+  private static void printArrayElements(Doc doc, Query query, AhatArrayInstance array) {
     doc.section("Array Elements");
-    doc.table(new Column("Index", Column.Align.RIGHT), new Column("Value"));
-    List<Object> elements = Arrays.asList(array.getValues());
-    SubsetSelector<Object> selector = new SubsetSelector(query, ARRAY_ELEMENTS_ID, elements);
+    AhatInstance base = array.getBaseline();
+    boolean diff = array.getBaseline() != array && !base.isPlaceHolder();
+    doc.table(
+        new Column("Index", Column.Align.RIGHT),
+        new Column("Value"),
+        new Column("Δ", Column.Align.LEFT, diff));
+
+    List<Value> elements = array.getValues();
+    SubsetSelector<Value> selector = new SubsetSelector(query, ARRAY_ELEMENTS_ID, elements);
     int i = 0;
-    for (Object elem : selector.selected()) {
-      doc.row(DocString.format("%d", i), Value.render(snapshot, elem));
+    for (Value current : selector.selected()) {
+      DocString delta = new DocString();
+      if (diff) {
+        Value previous = Value.getBaseline(base.asArrayInstance().getValue(i));
+        if (!Objects.equals(current, previous)) {
+          delta.append("was ");
+          delta.append(Summarizer.summarize(previous));
+        }
+      }
+      doc.row(DocString.format("%d", i), Summarizer.summarize(current), delta);
       i++;
     }
     doc.end();
     selector.render(doc);
   }
 
-  private static void printClassInfo(
-      Doc doc, Query query, AhatSnapshot snapshot, ClassObj clsobj) {
+  private static void printFields(Doc doc, boolean diff, List<FieldValue> fields) {
+    doc.table(
+        new Column("Type"),
+        new Column("Name"),
+        new Column("Value"),
+        new Column("Δ", Column.Align.LEFT, diff));
+
+    for (FieldValue field : fields) {
+      Value current = field.getValue();
+      DocString value;
+      if (field.isPlaceHolder()) {
+        value = DocString.removed("del");
+      } else {
+        value = Summarizer.summarize(current);
+      }
+
+      DocString delta = new DocString();
+      FieldValue basefield = field.getBaseline();
+      if (basefield.isPlaceHolder()) {
+        delta.append(DocString.added("new"));
+      } else {
+        Value previous = Value.getBaseline(basefield.getValue());
+        if (!Objects.equals(current, previous)) {
+          delta.append("was ");
+          delta.append(Summarizer.summarize(previous));
+        }
+      }
+      doc.row(DocString.text(field.getType()), DocString.text(field.getName()), value, delta);
+    }
+    doc.end();
+  }
+
+  private static void printClassInfo(Doc doc, Query query, AhatClassObj clsobj) {
     doc.section("Class Info");
     doc.descriptions();
     doc.description(DocString.text("Super Class"),
-        Value.render(snapshot, clsobj.getSuperClassObj()));
+        Summarizer.summarize(clsobj.getSuperClassObj()));
     doc.description(DocString.text("Class Loader"),
-        Value.render(snapshot, clsobj.getClassLoader()));
+        Summarizer.summarize(clsobj.getClassLoader()));
     doc.end();
 
     doc.section("Static Fields");
-    doc.table(new Column("Type"), new Column("Name"), new Column("Value"));
-    List<Map.Entry<Field, Object>> fields
-      = new ArrayList<Map.Entry<Field, Object>>(clsobj.getStaticFieldValues().entrySet());
-    SubsetSelector<Map.Entry<Field, Object>> selector
-      = new SubsetSelector(query, STATIC_FIELDS_ID, fields);
-    for (Map.Entry<Field, Object> field : selector.selected()) {
-      doc.row(
-          DocString.text(field.getKey().getType().toString()),
-          DocString.text(field.getKey().getName()),
-          Value.render(snapshot, field.getValue()));
+    AhatInstance base = clsobj.getBaseline();
+    List<FieldValue> fields = clsobj.getStaticFieldValues();
+    if (!base.isPlaceHolder()) {
+      Diff.fields(fields, base.asClassObj().getStaticFieldValues());
     }
-    doc.end();
+    SubsetSelector<FieldValue> selector = new SubsetSelector(query, STATIC_FIELDS_ID, fields);
+    printFields(doc, clsobj != base && !base.isPlaceHolder(), selector.selected());
     selector.render(doc);
   }
 
-  private static void printReferences(
-      Doc doc, Query query, AhatSnapshot snapshot, Instance inst) {
+  private static void printReferences(Doc doc, Query query, AhatInstance inst) {
     doc.section("Objects with References to this Object");
     if (inst.getHardReverseReferences().isEmpty()) {
       doc.println(DocString.text("(none)"));
     } else {
       doc.table(new Column("Object"));
-      List<Instance> references = inst.getHardReverseReferences();
-      SubsetSelector<Instance> selector = new SubsetSelector(query, HARD_REFS_ID, references);
-      for (Instance ref : selector.selected()) {
-        doc.row(Value.render(snapshot, ref));
+      List<AhatInstance> references = inst.getHardReverseReferences();
+      SubsetSelector<AhatInstance> selector = new SubsetSelector(query, HARD_REFS_ID, references);
+      for (AhatInstance ref : selector.selected()) {
+        doc.row(Summarizer.summarize(ref));
       }
       doc.end();
       selector.render(doc);
     }
 
-    if (inst.getSoftReverseReferences() != null) {
+    if (!inst.getSoftReverseReferences().isEmpty()) {
       doc.section("Objects with Soft References to this Object");
       doc.table(new Column("Object"));
-      List<Instance> references = inst.getSoftReverseReferences();
-      SubsetSelector<Instance> selector = new SubsetSelector(query, SOFT_REFS_ID, references);
-      for (Instance ref : selector.selected()) {
-        doc.row(Value.render(snapshot, ref));
+      List<AhatInstance> references = inst.getSoftReverseReferences();
+      SubsetSelector<AhatInstance> selector = new SubsetSelector(query, SOFT_REFS_ID, references);
+      for (AhatInstance ref : selector.selected()) {
+        doc.row(Summarizer.summarize(ref));
       }
       doc.end();
       selector.render(doc);
     }
   }
 
-  private void printAllocationSite(Doc doc, Query query, Instance inst) {
+  private void printAllocationSite(Doc doc, Query query, AhatInstance inst) {
     doc.section("Allocation Site");
-    Site site = mSnapshot.getSiteForInstance(inst);
+    Site site = inst.getSite();
     SitePrinter.printSite(mSnapshot, doc, query, ALLOCATION_SITE_ID, site);
   }
 
   // Draw the bitmap corresponding to this instance if there is one.
-  private static void printBitmap(Doc doc, Instance inst) {
-    Instance bitmap = InstanceUtils.getAssociatedBitmapInstance(inst);
+  private static void printBitmap(Doc doc, AhatInstance inst) {
+    AhatInstance bitmap = inst.getAssociatedBitmapInstance();
     if (bitmap != null) {
       doc.section("Bitmap Image");
       doc.println(DocString.image(
@@ -203,25 +246,25 @@
     }
   }
 
-  private void printGcRootPath(Doc doc, Query query, Instance inst) {
+  private void printGcRootPath(Doc doc, Query query, AhatInstance inst) {
     doc.section("Sample Path from GC Root");
-    List<PathElement> path = InstanceUtils.getPathFromGcRoot(inst);
+    List<PathElement> path = inst.getPathFromGcRoot();
 
-    // Add 'null' as a marker for the root.
-    path.add(0, null);
+    // Add a dummy PathElement as a marker for the root.
+    final PathElement root = new PathElement(null, null);
+    path.add(0, root);
 
     HeapTable.TableConfig<PathElement> table = new HeapTable.TableConfig<PathElement>() {
       public String getHeapsDescription() {
         return "Bytes Retained by Heap (Dominators Only)";
       }
 
-      public long getSize(PathElement element, Heap heap) {
-        if (element == null) {
-          return mSnapshot.getHeapSize(heap);
+      public long getSize(PathElement element, AhatHeap heap) {
+        if (element == root) {
+          return heap.getSize();
         }
         if (element.isDominator) {
-          int index = mSnapshot.getHeapIndex(heap);
-          return element.instance.getRetainedSize(index);
+          return element.instance.getRetainedSize(heap);
         }
         return 0;
       }
@@ -233,11 +276,11 @@
           }
 
           public DocString render(PathElement element) {
-            if (element == null) {
+            if (element == root) {
               return DocString.link(DocString.uri("rooted"), DocString.text("ROOT"));
             } else {
-              DocString label = DocString.text(" → ");
-              label.append(Value.render(mSnapshot, element.instance));
+              DocString label = DocString.text("→ ");
+              label.append(Summarizer.summarize(element.instance));
               label.append(element.field);
               return label;
             }
@@ -249,9 +292,9 @@
     HeapTable.render(doc, query, DOMINATOR_PATH_ID, table, mSnapshot, path);
   }
 
-  public void printDominatedObjects(Doc doc, Query query, Instance inst) {
+  public void printDominatedObjects(Doc doc, Query query, AhatInstance inst) {
     doc.section("Immediately Dominated Objects");
-    List<Instance> instances = mSnapshot.getDominated(inst);
+    List<AhatInstance> instances = inst.getDominated();
     if (instances != null) {
       DominatedList.render(mSnapshot, doc, query, DOMINATED_OBJECTS_ID, instances);
     } else {
diff --git a/tools/ahat/src/ObjectsHandler.java b/tools/ahat/src/ObjectsHandler.java
index 4cfb0a5..3062d23 100644
--- a/tools/ahat/src/ObjectsHandler.java
+++ b/tools/ahat/src/ObjectsHandler.java
@@ -16,7 +16,10 @@
 
 package com.android.ahat;
 
-import com.android.tools.perflib.heap.Instance;
+import com.android.ahat.heapdump.AhatInstance;
+import com.android.ahat.heapdump.AhatSnapshot;
+import com.android.ahat.heapdump.Site;
+import com.android.ahat.heapdump.Sort;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -33,17 +36,16 @@
 
   @Override
   public void handle(Doc doc, Query query) throws IOException {
-    int stackId = query.getInt("stack", 0);
+    int id = query.getInt("id", 0);
     int depth = query.getInt("depth", 0);
     String className = query.get("class", null);
     String heapName = query.get("heap", null);
-    Site site = mSnapshot.getSite(stackId, depth);
+    Site site = mSnapshot.getSite(id, depth);
 
-    List<Instance> insts = new ArrayList<Instance>();
-    for (Instance inst : site.getObjects()) {
+    List<AhatInstance> insts = new ArrayList<AhatInstance>();
+    for (AhatInstance inst : site.getObjects()) {
       if ((heapName == null || inst.getHeap().getName().equals(heapName))
-          && (className == null
-            || AhatSnapshot.getClassName(inst.getClassObj()).equals(className))) {
+          && (className == null || inst.getClassName().equals(className))) {
         insts.add(inst);
       }
     }
@@ -51,16 +53,22 @@
     Collections.sort(insts, Sort.defaultInstanceCompare(mSnapshot));
 
     doc.title("Objects");
+
     doc.table(
         new Column("Size", Column.Align.RIGHT),
+        new Column("Δ", Column.Align.RIGHT, mSnapshot.isDiffed()),
         new Column("Heap"),
         new Column("Object"));
-    SubsetSelector<Instance> selector = new SubsetSelector(query, OBJECTS_ID, insts);
-    for (Instance inst : selector.selected()) {
+
+    SubsetSelector<AhatInstance> selector = new SubsetSelector(query, OBJECTS_ID, insts);
+    for (AhatInstance inst : selector.selected()) {
+      AhatInstance base = inst.getBaseline();
       doc.row(
-          DocString.format("%,d", inst.getSize()),
+          DocString.format("%,14d", inst.getSize()),
+          DocString.delta(inst.isPlaceHolder(), base.isPlaceHolder(),
+            inst.getSize(), base.getSize()),
           DocString.text(inst.getHeap().getName()),
-          Value.render(mSnapshot, inst));
+          Summarizer.summarize(inst));
     }
     doc.end();
     selector.render(doc);
diff --git a/tools/ahat/src/OverviewHandler.java b/tools/ahat/src/OverviewHandler.java
index 0dbad7e..ea305c4 100644
--- a/tools/ahat/src/OverviewHandler.java
+++ b/tools/ahat/src/OverviewHandler.java
@@ -16,9 +16,11 @@
 
 package com.android.ahat;
 
-import com.android.tools.perflib.heap.Heap;
-import java.io.IOException;
+import com.android.ahat.heapdump.AhatHeap;
+import com.android.ahat.heapdump.AhatSnapshot;
+import com.android.ahat.heapdump.Diffable;
 import java.io.File;
+import java.io.IOException;
 import java.util.Collections;
 import java.util.List;
 
@@ -28,10 +30,12 @@
 
   private AhatSnapshot mSnapshot;
   private File mHprof;
+  private File mBaseHprof;
 
-  public OverviewHandler(AhatSnapshot snapshot, File hprof) {
+  public OverviewHandler(AhatSnapshot snapshot, File hprof, File basehprof) {
     mSnapshot = snapshot;
     mHprof = hprof;
+    mBaseHprof = basehprof;
   }
 
   @Override
@@ -44,42 +48,40 @@
         DocString.text("ahat version"),
         DocString.format("ahat-%s", OverviewHandler.class.getPackage().getImplementationVersion()));
     doc.description(DocString.text("hprof file"), DocString.text(mHprof.toString()));
+    if (mBaseHprof != null) {
+      doc.description(DocString.text("baseline hprof file"), DocString.text(mBaseHprof.toString()));
+    }
     doc.end();
 
     doc.section("Heap Sizes");
     printHeapSizes(doc, query);
 
-    List<InstanceUtils.NativeAllocation> allocs = mSnapshot.getNativeAllocations();
-    if (!allocs.isEmpty()) {
-      doc.section("Registered Native Allocations");
-      long totalSize = 0;
-      for (InstanceUtils.NativeAllocation alloc : allocs) {
-        totalSize += alloc.size;
-      }
-      doc.descriptions();
-      doc.description(DocString.text("Number of Registered Native Allocations"),
-          DocString.format("%,14d", allocs.size()));
-      doc.description(DocString.text("Total Size of Registered Native Allocations"),
-          DocString.format("%,14d", totalSize));
-      doc.end();
-    }
-
     doc.big(Menu.getMenu());
   }
 
-  private void printHeapSizes(Doc doc, Query query) {
-    List<Object> dummy = Collections.singletonList(null);
+  private static class TableElem implements Diffable<TableElem> {
+    @Override public TableElem getBaseline() {
+      return this;
+    }
 
-    HeapTable.TableConfig<Object> table = new HeapTable.TableConfig<Object>() {
+    @Override public boolean isPlaceHolder() {
+      return false;
+    }
+  }
+
+  private void printHeapSizes(Doc doc, Query query) {
+    List<TableElem> dummy = Collections.singletonList(new TableElem());
+
+    HeapTable.TableConfig<TableElem> table = new HeapTable.TableConfig<TableElem>() {
       public String getHeapsDescription() {
         return "Bytes Retained by Heap";
       }
 
-      public long getSize(Object element, Heap heap) {
-        return mSnapshot.getHeapSize(heap);
+      public long getSize(TableElem element, AhatHeap heap) {
+        return heap.getSize();
       }
 
-      public List<HeapTable.ValueConfig<Object>> getValueConfigs() {
+      public List<HeapTable.ValueConfig<TableElem>> getValueConfigs() {
         return Collections.emptyList();
       }
     };
diff --git a/tools/ahat/src/RootedHandler.java b/tools/ahat/src/RootedHandler.java
index ec3272f..26451a3 100644
--- a/tools/ahat/src/RootedHandler.java
+++ b/tools/ahat/src/RootedHandler.java
@@ -16,6 +16,7 @@
 
 package com.android.ahat;
 
+import com.android.ahat.heapdump.AhatSnapshot;
 import java.io.IOException;
 
 class RootedHandler implements AhatHandler {
diff --git a/tools/ahat/src/Site.java b/tools/ahat/src/Site.java
deleted file mode 100644
index dbb84f6..0000000
--- a/tools/ahat/src/Site.java
+++ /dev/null
@@ -1,199 +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.
- */
-
-package com.android.ahat;
-
-import com.android.tools.perflib.heap.ClassObj;
-import com.android.tools.perflib.heap.Heap;
-import com.android.tools.perflib.heap.Instance;
-import com.android.tools.perflib.heap.StackFrame;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-
-class Site {
-  // The site that this site was directly called from.
-  // mParent is null for the root site.
-  private Site mParent;
-
-  // A description of the Site. Currently this is used to uniquely identify a
-  // site within its parent.
-  private String mName;
-
-  // To identify this site, we pick one stack trace where we have seen the
-  // site. mStackId is the id for that stack trace, and mStackDepth is the
-  // depth of this site in that stack trace.
-  // For the root site, mStackId is 0 and mStackDepth is 0.
-  private int mStackId;
-  private int mStackDepth;
-
-  // Mapping from heap name to the total size of objects allocated in this
-  // site (including child sites) on the given heap.
-  private Map<String, Long> mSizesByHeap;
-
-  // Mapping from child site name to child site.
-  private Map<String, Site> mChildren;
-
-  // List of all objects allocated in this site (including child sites).
-  private List<Instance> mObjects;
-  private List<ObjectsInfo> mObjectsInfos;
-  private Map<Heap, Map<ClassObj, ObjectsInfo>> mObjectsInfoMap;
-
-  public static class ObjectsInfo {
-    public Heap heap;
-    public ClassObj classObj;
-    public long numInstances;
-    public long numBytes;
-
-    public ObjectsInfo(Heap heap, ClassObj classObj, long numInstances, long numBytes) {
-      this.heap = heap;
-      this.classObj = classObj;
-      this.numInstances = numInstances;
-      this.numBytes = numBytes;
-    }
-  }
-
-  /**
-   * Construct a root site.
-   */
-  public Site(String name) {
-    this(null, name, 0, 0);
-  }
-
-  public Site(Site parent, String name, int stackId, int stackDepth) {
-    mParent = parent;
-    mName = name;
-    mStackId = stackId;
-    mStackDepth = stackDepth;
-    mSizesByHeap = new HashMap<String, Long>();
-    mChildren = new HashMap<String, Site>();
-    mObjects = new ArrayList<Instance>();
-    mObjectsInfos = new ArrayList<ObjectsInfo>();
-    mObjectsInfoMap = new HashMap<Heap, Map<ClassObj, ObjectsInfo>>();
-  }
-
-  /**
-   * Add an instance to this site.
-   * Returns the site at which the instance was allocated.
-   */
-  public Site add(int stackId, int stackDepth, Iterator<StackFrame> path, Instance inst) {
-    mObjects.add(inst);
-
-    String heap = inst.getHeap().getName();
-    mSizesByHeap.put(heap, getSize(heap) + inst.getSize());
-
-    Map<ClassObj, ObjectsInfo> classToObjectsInfo = mObjectsInfoMap.get(inst.getHeap());
-    if (classToObjectsInfo == null) {
-      classToObjectsInfo = new HashMap<ClassObj, ObjectsInfo>();
-      mObjectsInfoMap.put(inst.getHeap(), classToObjectsInfo);
-    }
-
-    ObjectsInfo info = classToObjectsInfo.get(inst.getClassObj());
-    if (info == null) {
-      info = new ObjectsInfo(inst.getHeap(), inst.getClassObj(), 0, 0);
-      mObjectsInfos.add(info);
-      classToObjectsInfo.put(inst.getClassObj(), info);
-    }
-
-    info.numInstances++;
-    info.numBytes += inst.getSize();
-
-    if (path.hasNext()) {
-      String next = path.next().toString();
-      Site child = mChildren.get(next);
-      if (child == null) {
-        child = new Site(this, next, stackId, stackDepth + 1);
-        mChildren.put(next, child);
-      }
-      return child.add(stackId, stackDepth + 1, path, inst);
-    } else {
-      return this;
-    }
-  }
-
-  // Get the size of a site for a specific heap.
-  public long getSize(String heap) {
-    Long val = mSizesByHeap.get(heap);
-    if (val == null) {
-      return 0;
-    }
-    return val;
-  }
-
-  /**
-   * Get the list of objects allocated under this site. Includes objects
-   * allocated in children sites.
-   */
-  public Collection<Instance> getObjects() {
-    return mObjects;
-  }
-
-  public List<ObjectsInfo> getObjectsInfos() {
-    return mObjectsInfos;
-  }
-
-  // Get the combined size of the site for all heaps.
-  public long getTotalSize() {
-    long size = 0;
-    for (Long val : mSizesByHeap.values()) {
-      size += val;
-    }
-    return size;
-  }
-
-  /**
-   * Return the site this site was called from.
-   * Returns null for the root site.
-   */
-  public Site getParent() {
-    return mParent;
-  }
-
-  public String getName() {
-    return mName;
-  }
-
-  // Returns the hprof id of a stack this site appears on.
-  public int getStackId() {
-    return mStackId;
-  }
-
-  // Returns the stack depth of this site in the stack whose id is returned
-  // by getStackId().
-  public int getStackDepth() {
-    return mStackDepth;
-  }
-
-  List<Site> getChildren() {
-    return new ArrayList<Site>(mChildren.values());
-  }
-
-  // Get the child at the given path relative to this site.
-  // Returns null if no such child found.
-  Site getChild(Iterator<StackFrame> path) {
-    if (path.hasNext()) {
-      String next = path.next().toString();
-      Site child = mChildren.get(next);
-      return (child == null) ? null : child.getChild(path);
-    } else {
-      return this;
-    }
-  }
-}
diff --git a/tools/ahat/src/SiteHandler.java b/tools/ahat/src/SiteHandler.java
index 839e220..febf171 100644
--- a/tools/ahat/src/SiteHandler.java
+++ b/tools/ahat/src/SiteHandler.java
@@ -16,7 +16,10 @@
 
 package com.android.ahat;
 
-import com.android.tools.perflib.heap.Heap;
+import com.android.ahat.heapdump.AhatHeap;
+import com.android.ahat.heapdump.AhatSnapshot;
+import com.android.ahat.heapdump.Site;
+import com.android.ahat.heapdump.Sort;
 import java.io.IOException;
 import java.util.Collections;
 import java.util.Comparator;
@@ -35,11 +38,13 @@
 
   @Override
   public void handle(Doc doc, Query query) throws IOException {
-    int stackId = query.getInt("stack", 0);
-    int depth = query.getInt("depth", -1);
-    Site site = mSnapshot.getSite(stackId, depth);
+    int id = query.getInt("id", 0);
+    int depth = query.getInt("depth", 0);
+    Site site = mSnapshot.getSite(id, depth);
 
-    doc.title("Site %s", site.getName());
+    doc.title("Site");
+    doc.big(Summarizer.summarize(site));
+
     doc.section("Allocation Site");
     SitePrinter.printSite(mSnapshot, doc, query, ALLOCATION_SITE_ID, site);
 
@@ -48,15 +53,14 @@
     if (children.isEmpty()) {
       doc.println(DocString.text("(none)"));
     } else {
-      Collections.sort(children, new Sort.SiteBySize("app"));
-
+      Collections.sort(children, Sort.defaultSiteCompare(mSnapshot));
       HeapTable.TableConfig<Site> table = new HeapTable.TableConfig<Site>() {
         public String getHeapsDescription() {
           return "Reachable Bytes Allocated on Heap";
         }
 
-        public long getSize(Site element, Heap heap) {
-          return element.getSize(heap.getName());
+        public long getSize(Site element, AhatHeap heap) {
+          return element.getSize(heap);
         }
 
         public List<HeapTable.ValueConfig<Site>> getValueConfigs() {
@@ -66,10 +70,7 @@
             }
 
             public DocString render(Site element) {
-              return DocString.link(
-                  DocString.formattedUri("site?stack=%d&depth=%d",
-                    element.getStackId(), element.getStackDepth()),
-                  DocString.text(element.getName()));
+              return Summarizer.summarize(element);
             }
           };
           return Collections.singletonList(value);
@@ -79,29 +80,36 @@
     }
 
     doc.section("Objects Allocated");
+
     doc.table(
         new Column("Reachable Bytes Allocated", Column.Align.RIGHT),
+        new Column("Δ", Column.Align.RIGHT, mSnapshot.isDiffed()),
         new Column("Instances", Column.Align.RIGHT),
+        new Column("Δ", Column.Align.RIGHT, mSnapshot.isDiffed()),
         new Column("Heap"),
         new Column("Class"));
+
     List<Site.ObjectsInfo> infos = site.getObjectsInfos();
     Comparator<Site.ObjectsInfo> compare = new Sort.WithPriority<Site.ObjectsInfo>(
-        new Sort.ObjectsInfoByHeapName(),
-        new Sort.ObjectsInfoBySize(),
-        new Sort.ObjectsInfoByClassName());
+        Sort.OBJECTS_INFO_BY_HEAP_NAME,
+        Sort.OBJECTS_INFO_BY_SIZE,
+        Sort.OBJECTS_INFO_BY_CLASS_NAME);
     Collections.sort(infos, compare);
     SubsetSelector<Site.ObjectsInfo> selector
       = new SubsetSelector(query, OBJECTS_ALLOCATED_ID, infos);
     for (Site.ObjectsInfo info : selector.selected()) {
-      String className = AhatSnapshot.getClassName(info.classObj);
+      Site.ObjectsInfo baseinfo = info.getBaseline();
+      String className = info.getClassName();
       doc.row(
           DocString.format("%,14d", info.numBytes),
+          DocString.delta(false, false, info.numBytes, baseinfo.numBytes),
           DocString.link(
-            DocString.formattedUri("objects?stack=%d&depth=%d&heap=%s&class=%s",
-                site.getStackId(), site.getStackDepth(), info.heap.getName(), className),
+            DocString.formattedUri("objects?id=%d&depth=%d&heap=%s&class=%s",
+              site.getId(), site.getDepth(), info.heap.getName(), className),
             DocString.format("%,14d", info.numInstances)),
+          DocString.delta(false, false, info.numInstances, baseinfo.numInstances),
           DocString.text(info.heap.getName()),
-          Value.render(mSnapshot, info.classObj));
+          Summarizer.summarize(info.classObj));
     }
     doc.end();
     selector.render(doc);
diff --git a/tools/ahat/src/SitePrinter.java b/tools/ahat/src/SitePrinter.java
index 2c06b47..21ca2de 100644
--- a/tools/ahat/src/SitePrinter.java
+++ b/tools/ahat/src/SitePrinter.java
@@ -16,7 +16,9 @@
 
 package com.android.ahat;
 
-import com.android.tools.perflib.heap.Heap;
+import com.android.ahat.heapdump.AhatHeap;
+import com.android.ahat.heapdump.AhatSnapshot;
+import com.android.ahat.heapdump.Site;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -35,8 +37,8 @@
         return "Reachable Bytes Allocated on Heap";
       }
 
-      public long getSize(Site element, Heap heap) {
-        return element.getSize(heap.getName());
+      public long getSize(Site element, AhatHeap heap) {
+        return element.getSize(heap);
       }
 
       public List<HeapTable.ValueConfig<Site>> getValueConfigs() {
@@ -50,11 +52,7 @@
             if (element.getParent() != null) {
               str.append("→ ");
             }
-            str.appendLink(
-                DocString.formattedUri("site?stack=%d&depth=%d",
-                    element.getStackId(), element.getStackDepth()),
-                DocString.text(element.getName()));
-            return str;
+            return str.append(Summarizer.summarize(element));
           }
         };
         return Collections.singletonList(value);
diff --git a/tools/ahat/src/Sort.java b/tools/ahat/src/Sort.java
deleted file mode 100644
index 8a3d9f2..0000000
--- a/tools/ahat/src/Sort.java
+++ /dev/null
@@ -1,208 +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.
- */
-
-package com.android.ahat;
-
-import com.android.tools.perflib.heap.Heap;
-import com.android.tools.perflib.heap.Instance;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Comparator;
-import java.util.Iterator;
-import java.util.List;
-
-/**
- * Provides Comparators and helper functions for sorting Instances, Sites, and
- * other things.
- *
- * Note: The Comparators defined here impose orderings that are inconsistent
- * with equals. They should not be used for element lookup or search. They
- * should only be used for showing elements to the user in different orders.
- */
-class Sort {
-  /**
-   * Compare instances by their instance id.
-   * This sorts instances from smaller id to larger id.
-   */
-  public static class InstanceById implements Comparator<Instance> {
-    @Override
-    public int compare(Instance a, Instance b) {
-      return Long.compare(a.getId(), b.getId());
-    }
-  }
-
-  /**
-   * Compare instances by their total retained size.
-   * Different instances with the same total retained size are considered
-   * equal for the purposes of comparison.
-   * This sorts instances from larger retained size to smaller retained size.
-   */
-  public static class InstanceByTotalRetainedSize implements Comparator<Instance> {
-    @Override
-    public int compare(Instance a, Instance b) {
-      return Long.compare(b.getTotalRetainedSize(), a.getTotalRetainedSize());
-    }
-  }
-
-  /**
-   * Compare instances by their retained size for a given heap index.
-   * Different instances with the same total retained size are considered
-   * equal for the purposes of comparison.
-   * This sorts instances from larger retained size to smaller retained size.
-   */
-  public static class InstanceByHeapRetainedSize implements Comparator<Instance> {
-    private int mIndex;
-
-    public InstanceByHeapRetainedSize(AhatSnapshot snapshot, Heap heap) {
-      mIndex = snapshot.getHeapIndex(heap);
-    }
-
-    public InstanceByHeapRetainedSize(int heapIndex) {
-      mIndex = heapIndex;
-    }
-
-    @Override
-    public int compare(Instance a, Instance b) {
-      return Long.compare(b.getRetainedSize(mIndex), a.getRetainedSize(mIndex));
-    }
-  }
-
-  /**
-   * Compare objects based on a list of comparators, giving priority to the
-   * earlier comparators in the list.
-   */
-  public static class WithPriority<T> implements Comparator<T> {
-    private List<Comparator<T>> mComparators;
-
-    public WithPriority(Comparator<T>... comparators) {
-      mComparators = Arrays.asList(comparators);
-    }
-
-    public WithPriority(List<Comparator<T>> comparators) {
-      mComparators = comparators;
-    }
-
-    @Override
-    public int compare(T a, T b) {
-      int res = 0;
-      Iterator<Comparator<T>> iter = mComparators.iterator();
-      while (res == 0 && iter.hasNext()) {
-        res = iter.next().compare(a, b);
-      }
-      return res;
-    }
-  }
-
-  public static Comparator<Instance> defaultInstanceCompare(AhatSnapshot snapshot) {
-    List<Comparator<Instance>> comparators = new ArrayList<Comparator<Instance>>();
-
-    // Priority goes to the app heap, if we can find one.
-    Heap appHeap = snapshot.getHeap("app");
-    if (appHeap != null) {
-      comparators.add(new InstanceByHeapRetainedSize(snapshot, appHeap));
-    }
-
-    // Next is by total retained size.
-    comparators.add(new InstanceByTotalRetainedSize());
-    return new WithPriority<Instance>(comparators);
-  }
-
-  /**
-   * Compare Sites by the size of objects allocated on a given heap.
-   * Different object infos with the same size on the given heap are
-   * considered equal for the purposes of comparison.
-   * This sorts sites from larger size to smaller size.
-   */
-  public static class SiteBySize implements Comparator<Site> {
-    String mHeap;
-
-    public SiteBySize(String heap) {
-      mHeap = heap;
-    }
-
-    @Override
-    public int compare(Site a, Site b) {
-      return Long.compare(b.getSize(mHeap), a.getSize(mHeap));
-    }
-  }
-
-  /**
-   * Compare Site.ObjectsInfo by their size.
-   * Different object infos with the same total retained size are considered
-   * equal for the purposes of comparison.
-   * This sorts object infos from larger retained size to smaller size.
-   */
-  public static class ObjectsInfoBySize implements Comparator<Site.ObjectsInfo> {
-    @Override
-    public int compare(Site.ObjectsInfo a, Site.ObjectsInfo b) {
-      return Long.compare(b.numBytes, a.numBytes);
-    }
-  }
-
-  /**
-   * Compare Site.ObjectsInfo by heap name.
-   * Different object infos with the same heap name are considered equal for
-   * the purposes of comparison.
-   */
-  public static class ObjectsInfoByHeapName implements Comparator<Site.ObjectsInfo> {
-    @Override
-    public int compare(Site.ObjectsInfo a, Site.ObjectsInfo b) {
-      return a.heap.getName().compareTo(b.heap.getName());
-    }
-  }
-
-  /**
-   * Compare Site.ObjectsInfo by class name.
-   * Different object infos with the same class name are considered equal for
-   * the purposes of comparison.
-   */
-  public static class ObjectsInfoByClassName implements Comparator<Site.ObjectsInfo> {
-    @Override
-    public int compare(Site.ObjectsInfo a, Site.ObjectsInfo b) {
-      String aName = AhatSnapshot.getClassName(a.classObj);
-      String bName = AhatSnapshot.getClassName(b.classObj);
-      return aName.compareTo(bName);
-    }
-  }
-
-  /**
-   * Compare AhatSnapshot.NativeAllocation by heap name.
-   * Different allocations with the same heap name are considered equal for
-   * the purposes of comparison.
-   */
-  public static class NativeAllocationByHeapName
-      implements Comparator<InstanceUtils.NativeAllocation> {
-    @Override
-    public int compare(InstanceUtils.NativeAllocation a, InstanceUtils.NativeAllocation b) {
-      return a.heap.getName().compareTo(b.heap.getName());
-    }
-  }
-
-  /**
-   * Compare InstanceUtils.NativeAllocation by their size.
-   * Different allocations with the same size are considered equal for the
-   * purposes of comparison.
-   * This sorts allocations from larger size to smaller size.
-   */
-  public static class NativeAllocationBySize implements Comparator<InstanceUtils.NativeAllocation> {
-    @Override
-    public int compare(InstanceUtils.NativeAllocation a, InstanceUtils.NativeAllocation b) {
-      return Long.compare(b.size, a.size);
-    }
-  }
-}
-
diff --git a/tools/ahat/src/StaticHandler.java b/tools/ahat/src/StaticHandler.java
index fb7049d..b2805d6 100644
--- a/tools/ahat/src/StaticHandler.java
+++ b/tools/ahat/src/StaticHandler.java
@@ -17,10 +17,10 @@
 package com.android.ahat;
 
 import com.google.common.io.ByteStreams;
-import com.sun.net.httpserver.HttpHandler;
 import com.sun.net.httpserver.HttpExchange;
-import java.io.InputStream;
+import com.sun.net.httpserver.HttpHandler;
 import java.io.IOException;
+import java.io.InputStream;
 import java.io.OutputStream;
 import java.io.PrintStream;
 
diff --git a/tools/ahat/src/Summarizer.java b/tools/ahat/src/Summarizer.java
new file mode 100644
index 0000000..016eab4
--- /dev/null
+++ b/tools/ahat/src/Summarizer.java
@@ -0,0 +1,143 @@
+/*
+ * 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.
+ */
+
+package com.android.ahat;
+
+import com.android.ahat.heapdump.AhatInstance;
+import com.android.ahat.heapdump.Site;
+import com.android.ahat.heapdump.Value;
+import java.net.URI;
+
+/**
+ * Class for generating a DocString summary of an instance or value.
+ */
+class Summarizer {
+
+  // For string literals, we limit the number of characters we show to
+  // kMaxChars in case the string is really long.
+  private static int kMaxChars = 200;
+
+  /**
+   * Creates a DocString representing a summary of the given instance.
+   */
+  public static DocString summarize(AhatInstance inst) {
+    DocString formatted = new DocString();
+    if (inst == null) {
+      formatted.append("null");
+      return formatted;
+    }
+
+    // Annotate new objects as new.
+    if (inst.getBaseline().isPlaceHolder()) {
+      formatted.append(DocString.added("new "));
+    }
+
+    // Annotate deleted objects as deleted.
+    if (inst.isPlaceHolder()) {
+      formatted.append(DocString.removed("del "));
+    }
+
+    // Annotate unreachable objects as such.
+    if (!inst.isReachable()) {
+      formatted.append("unreachable ");
+    }
+
+    // Annotate roots as roots.
+    if (inst.isRoot()) {
+      formatted.append("root ");
+    }
+
+    // Annotate classes as classes.
+    DocString linkText = new DocString();
+    if (inst.isClassObj()) {
+      linkText.append("class ");
+    }
+
+    linkText.append(inst.toString());
+
+    if (inst.isPlaceHolder()) {
+      // Don't make links to placeholder objects.
+      formatted.append(linkText);
+    } else {
+      URI objTarget = DocString.formattedUri("object?id=%d", inst.getId());
+      formatted.appendLink(objTarget, linkText);
+    }
+
+    // Annotate Strings with their values.
+    String stringValue = inst.asString(kMaxChars);
+    if (stringValue != null) {
+      formatted.appendFormat(" \"%s", stringValue);
+      formatted.append(kMaxChars == stringValue.length() ? "..." : "\"");
+    }
+
+    // Annotate Reference with its referent
+    AhatInstance referent = inst.getReferent();
+    if (referent != null) {
+      formatted.append(" for ");
+
+      // It should not be possible for a referent to refer back to the
+      // reference object, even indirectly, so there shouldn't be any issues
+      // with infinite recursion here.
+      formatted.append(summarize(referent));
+    }
+
+    // Annotate DexCache with its location.
+    String dexCacheLocation = inst.getDexCacheLocation(kMaxChars);
+    if (dexCacheLocation != null) {
+      formatted.appendFormat(" for %s", dexCacheLocation);
+      if (kMaxChars == dexCacheLocation.length()) {
+        formatted.append("...");
+      }
+    }
+
+    // Annotate bitmaps with a thumbnail.
+    AhatInstance bitmap = inst.getAssociatedBitmapInstance();
+    String thumbnail = "";
+    if (bitmap != null) {
+      URI uri = DocString.formattedUri("bitmap?id=%d", bitmap.getId());
+      formatted.appendThumbnail(uri, "bitmap image");
+    }
+    return formatted;
+  }
+
+  /**
+   * Creates a DocString summarizing the given value.
+   */
+  public static DocString summarize(Value value) {
+    if (value == null) {
+      return DocString.text("null");
+    }
+    if (value.isAhatInstance()) {
+      return summarize(value.asAhatInstance());
+    }
+    return DocString.text(value.toString());
+  }
+
+  /**
+   * Creates a DocString summarizing the given site.
+   */
+  public static DocString summarize(Site site) {
+    DocString text = DocString.text(site.getMethodName());
+    text.append(site.getSignature());
+    text.append(" - ");
+    text.append(site.getFilename());
+    if (site.getLineNumber() > 0) {
+      text.append(":").append(Integer.toString(site.getLineNumber()));
+    }
+    URI uri = DocString.formattedUri("site?id=%d&depth=%d", site.getId(), site.getDepth());
+    return DocString.link(uri, text);
+  }
+}
diff --git a/tools/ahat/src/Value.java b/tools/ahat/src/Value.java
deleted file mode 100644
index 847692b..0000000
--- a/tools/ahat/src/Value.java
+++ /dev/null
@@ -1,107 +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.
- */
-
-package com.android.ahat;
-
-import com.android.tools.perflib.heap.ClassObj;
-import com.android.tools.perflib.heap.Instance;
-import java.net.URI;
-
-/**
- * Class to render an hprof value to a DocString.
- */
-class Value {
-
-  // For string literals, we limit the number of characters we show to
-  // kMaxChars in case the string is really long.
-  private static int kMaxChars = 200;
-
-  /**
-   * Create a DocString representing a summary of the given instance.
-   */
-  private static DocString renderInstance(AhatSnapshot snapshot, Instance inst) {
-    DocString formatted = new DocString();
-    if (inst == null) {
-      formatted.append("(null)");
-      return formatted;
-    }
-
-    // Annotate roots as roots.
-    if (snapshot.isRoot(inst)) {
-      formatted.append("(root) ");
-    }
-
-
-    // Annotate classes as classes.
-    DocString link = new DocString();
-    if (inst instanceof ClassObj) {
-      link.append("class ");
-    }
-
-    link.append(inst.toString());
-
-    URI objTarget = DocString.formattedUri("object?id=%d", inst.getId());
-    formatted.appendLink(objTarget, link);
-
-    // Annotate Strings with their values.
-    String stringValue = InstanceUtils.asString(inst, kMaxChars);
-    if (stringValue != null) {
-      formatted.appendFormat(" \"%s", stringValue);
-      formatted.append(kMaxChars == stringValue.length() ? "..." : "\"");
-    }
-
-    // Annotate Reference with its referent
-    Instance referent = InstanceUtils.getReferent(inst);
-    if (referent != null) {
-      formatted.append(" for ");
-
-      // It should not be possible for a referent to refer back to the
-      // reference object, even indirectly, so there shouldn't be any issues
-      // with infinite recursion here.
-      formatted.append(renderInstance(snapshot, referent));
-    }
-
-    // Annotate DexCache with its location.
-    String dexCacheLocation = InstanceUtils.getDexCacheLocation(inst, kMaxChars);
-    if (dexCacheLocation != null) {
-      formatted.appendFormat(" for %s", dexCacheLocation);
-      if (kMaxChars == dexCacheLocation.length()) {
-        formatted.append("...");
-      }
-    }
-
-
-    // Annotate bitmaps with a thumbnail.
-    Instance bitmap = InstanceUtils.getAssociatedBitmapInstance(inst);
-    String thumbnail = "";
-    if (bitmap != null) {
-      URI uri = DocString.formattedUri("bitmap?id=%d", bitmap.getId());
-      formatted.appendThumbnail(uri, "bitmap image");
-    }
-    return formatted;
-  }
-
-  /**
-   * Create a DocString summarizing the given value.
-   */
-  public static DocString render(AhatSnapshot snapshot, Object val) {
-    if (val instanceof Instance) {
-      return renderInstance(snapshot, (Instance)val);
-    } else {
-      return DocString.format("%s", val);
-    }
-  }
-}
diff --git a/tools/ahat/src/heapdump/AhatArrayInstance.java b/tools/ahat/src/heapdump/AhatArrayInstance.java
new file mode 100644
index 0000000..d88cf94
--- /dev/null
+++ b/tools/ahat/src/heapdump/AhatArrayInstance.java
@@ -0,0 +1,229 @@
+/*
+ * 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.
+ */
+
+package com.android.ahat.heapdump;
+
+import com.android.tools.perflib.heap.ArrayInstance;
+import com.android.tools.perflib.heap.Instance;
+import java.nio.charset.StandardCharsets;
+import java.util.AbstractList;
+import java.util.List;
+
+public class AhatArrayInstance extends AhatInstance {
+  // To save space, we store byte, character, and object arrays directly as
+  // byte, character, and AhatInstance arrays respectively. This is especially
+  // important for large byte arrays, such as bitmaps. All other array types
+  // are stored as an array of objects, though we could potentially save space
+  // by specializing those too. mValues is a list view of the underlying
+  // array.
+  private List<Value> mValues;
+  private byte[] mByteArray;    // null if not a byte array.
+  private char[] mCharArray;    // null if not a char array.
+
+  public AhatArrayInstance(long id) {
+    super(id);
+  }
+
+  @Override void initialize(AhatSnapshot snapshot, Instance inst) {
+    super.initialize(snapshot, inst);
+
+    ArrayInstance array = (ArrayInstance)inst;
+    switch (array.getArrayType()) {
+      case OBJECT:
+        Object[] objects = array.getValues();
+        final AhatInstance[] insts = new AhatInstance[objects.length];
+        for (int i = 0; i < objects.length; i++) {
+          if (objects[i] != null) {
+            Instance ref = (Instance)objects[i];
+            insts[i] = snapshot.findInstance(ref.getId());
+            if (ref.getNextInstanceToGcRoot() == inst) {
+              String field = "[" + Integer.toString(i) + "]";
+              insts[i].setNextInstanceToGcRoot(this, field);
+            }
+          }
+        }
+        mValues = new AbstractList<Value>() {
+          @Override public int size() {
+            return insts.length;
+          }
+
+          @Override public Value get(int index) {
+            AhatInstance obj = insts[index];
+            return obj == null ? null : new Value(insts[index]);
+          }
+        };
+        break;
+
+      case CHAR:
+        final char[] chars = array.asCharArray(0, array.getLength());
+        mCharArray = chars;
+        mValues = new AbstractList<Value>() {
+          @Override public int size() {
+            return chars.length;
+          }
+
+          @Override public Value get(int index) {
+            return new Value(chars[index]);
+          }
+        };
+        break;
+
+      case BYTE:
+        final byte[] bytes = array.asRawByteArray(0, array.getLength());
+        mByteArray = bytes;
+        mValues = new AbstractList<Value>() {
+          @Override public int size() {
+            return bytes.length;
+          }
+
+          @Override public Value get(int index) {
+            return new Value(bytes[index]);
+          }
+        };
+        break;
+
+      default:
+        final Object[] values = array.getValues();
+        mValues = new AbstractList<Value>() {
+          @Override public int size() {
+            return values.length;
+          }
+
+          @Override public Value get(int index) {
+            Object obj = values[index];
+            return obj == null ? null : new Value(obj);
+          }
+        };
+        break;
+    }
+  }
+
+  /**
+   * Returns the length of the array.
+   */
+  public int getLength() {
+    return mValues.size();
+  }
+
+  /**
+   * Returns the array's values.
+   */
+  public List<Value> getValues() {
+    return mValues;
+  }
+
+  /**
+   * Returns the object at the given index of this array.
+   */
+  public Value getValue(int index) {
+    return mValues.get(index);
+  }
+
+  @Override public boolean isArrayInstance() {
+    return true;
+  }
+
+  @Override public AhatArrayInstance asArrayInstance() {
+    return this;
+  }
+
+  @Override public String asString(int maxChars) {
+    return asString(0, getLength(), maxChars);
+  }
+
+  /**
+   * Returns the String value associated with this array.
+   * Only char arrays are considered as having an associated String value.
+   */
+  String asString(int offset, int count, int maxChars) {
+    if (mCharArray == null) {
+      return null;
+    }
+
+    if (count == 0) {
+      return "";
+    }
+    int numChars = mCharArray.length;
+    if (0 <= maxChars && maxChars < count) {
+      count = maxChars;
+    }
+
+    int end = offset + count - 1;
+    if (offset >= 0 && offset < numChars && end >= 0 && end < numChars) {
+      return new String(mCharArray, offset, count);
+    }
+    return null;
+  }
+
+  /**
+   * Returns the ascii String value associated with this array.
+   * Only byte arrays are considered as having an associated ascii String value.
+   */
+  String asAsciiString(int offset, int count, int maxChars) {
+    if (mByteArray == null) {
+      return null;
+    }
+
+    if (count == 0) {
+      return "";
+    }
+    int numChars = mByteArray.length;
+    if (0 <= maxChars && maxChars < count) {
+      count = maxChars;
+    }
+
+    int end = offset + count - 1;
+    if (offset >= 0 && offset < numChars && end >= 0 && end < numChars) {
+      return new String(mByteArray, offset, count, StandardCharsets.US_ASCII);
+    }
+    return null;
+  }
+
+  /**
+   * Returns the String value associated with this array. Byte arrays are
+   * considered as ascii encoded strings.
+   */
+  String asMaybeCompressedString(int offset, int count, int maxChars) {
+    String str = asString(offset, count, maxChars);
+    if (str == null) {
+      str = asAsciiString(offset, count, maxChars);
+    }
+    return str;
+  }
+
+  @Override public AhatInstance getAssociatedBitmapInstance() {
+    if (mByteArray != null) {
+      List<AhatInstance> refs = getHardReverseReferences();
+      if (refs.size() == 1) {
+        AhatInstance ref = refs.get(0);
+        return ref.getAssociatedBitmapInstance();
+      }
+    }
+    return null;
+  }
+
+  @Override public String toString() {
+    String className = getClassName();
+    if (className.endsWith("[]")) {
+      className = className.substring(0, className.length() - 2);
+    }
+    return String.format("%s[%d]@%08x", className, mValues.size(), getId());
+  }
+
+  byte[] asByteArray() {
+    return mByteArray;
+  }
+}
diff --git a/tools/ahat/src/heapdump/AhatClassInstance.java b/tools/ahat/src/heapdump/AhatClassInstance.java
new file mode 100644
index 0000000..273530a
--- /dev/null
+++ b/tools/ahat/src/heapdump/AhatClassInstance.java
@@ -0,0 +1,224 @@
+/*
+ * 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.
+ */
+
+package com.android.ahat.heapdump;
+
+import com.android.tools.perflib.heap.ClassInstance;
+import com.android.tools.perflib.heap.Instance;
+import java.awt.image.BufferedImage;
+import java.util.Arrays;
+import java.util.List;
+
+public class AhatClassInstance extends AhatInstance {
+  private FieldValue[] mFieldValues;
+
+  public AhatClassInstance(long id) {
+    super(id);
+  }
+
+  @Override void initialize(AhatSnapshot snapshot, Instance inst) {
+    super.initialize(snapshot, inst);
+
+    ClassInstance classInst = (ClassInstance)inst;
+    List<ClassInstance.FieldValue> fieldValues = classInst.getValues();
+    mFieldValues = new FieldValue[fieldValues.size()];
+    for (int i = 0; i < mFieldValues.length; i++) {
+      ClassInstance.FieldValue field = fieldValues.get(i);
+      String name = field.getField().getName();
+      String type = field.getField().getType().toString();
+      Value value = snapshot.getValue(field.getValue());
+
+      mFieldValues[i] = new FieldValue(name, type, value);
+
+      if (field.getValue() instanceof Instance) {
+        Instance ref = (Instance)field.getValue();
+        if (ref.getNextInstanceToGcRoot() == inst) {
+          value.asAhatInstance().setNextInstanceToGcRoot(this, "." + name);
+        }
+      }
+    }
+  }
+
+  @Override public Value getField(String fieldName) {
+    for (FieldValue field : mFieldValues) {
+      if (fieldName.equals(field.getName())) {
+        return field.getValue();
+      }
+    }
+    return null;
+  }
+
+  @Override public AhatInstance getRefField(String fieldName) {
+    Value value = getField(fieldName);
+    return value == null ? null : value.asAhatInstance();
+  }
+
+  /**
+   * Read an int field of an instance.
+   * The field is assumed to be an int type.
+   * Returns <code>def</code> if the field value is not an int or could not be
+   * read.
+   */
+  private Integer getIntField(String fieldName, Integer def) {
+    Value value = getField(fieldName);
+    if (value == null || !value.isInteger()) {
+      return def;
+    }
+    return value.asInteger();
+  }
+
+  /**
+   * Read a long field of this instance.
+   * The field is assumed to be a long type.
+   * Returns <code>def</code> if the field value is not an long or could not
+   * be read.
+   */
+  private Long getLongField(String fieldName, Long def) {
+    Value value = getField(fieldName);
+    if (value == null || !value.isLong()) {
+      return def;
+    }
+    return value.asLong();
+  }
+
+  /**
+   * Returns the list of class instance fields for this instance.
+   */
+  public List<FieldValue> getInstanceFields() {
+    return Arrays.asList(mFieldValues);
+  }
+
+  /**
+   * Returns true if this is an instance of a class with the given name.
+   */
+  private boolean isInstanceOfClass(String className) {
+    AhatClassObj cls = getClassObj();
+    while (cls != null) {
+      if (className.equals(cls.getName())) {
+        return true;
+      }
+      cls = cls.getSuperClassObj();
+    }
+    return false;
+  }
+
+  @Override public String asString(int maxChars) {
+    if (!isInstanceOfClass("java.lang.String")) {
+      return null;
+    }
+
+    Value value = getField("value");
+    if (!value.isAhatInstance()) {
+      return null;
+    }
+
+    AhatInstance inst = value.asAhatInstance();
+    if (inst.isArrayInstance()) {
+      AhatArrayInstance chars = inst.asArrayInstance();
+      int numChars = chars.getLength();
+      int count = getIntField("count", numChars);
+      int offset = getIntField("offset", 0);
+      return chars.asMaybeCompressedString(offset, count, maxChars);
+    }
+    return null;
+  }
+
+  @Override public AhatInstance getReferent() {
+    if (isInstanceOfClass("java.lang.ref.Reference")) {
+      return getRefField("referent");
+    }
+    return null;
+  }
+
+  @Override public String getDexCacheLocation(int maxChars) {
+    if (isInstanceOfClass("java.lang.DexCache")) {
+      AhatInstance location = getRefField("location");
+      if (location != null) {
+        return location.asString(maxChars);
+      }
+    }
+    return null;
+  }
+
+  @Override public AhatInstance getAssociatedBitmapInstance() {
+    if (isInstanceOfClass("android.graphics.Bitmap")) {
+      return this;
+    }
+    return null;
+  }
+
+  @Override public boolean isClassInstance() {
+    return true;
+  }
+
+  @Override public AhatClassInstance asClassInstance() {
+    return this;
+  }
+
+  @Override public String toString() {
+    return String.format("%s@%08x", getClassName(), getId());
+  }
+
+  /**
+   * Read the given field from the given instance.
+   * The field is assumed to be a byte[] field.
+   * Returns null if the field value is null, not a byte[] or could not be read.
+   */
+  private byte[] getByteArrayField(String fieldName) {
+    Value value = getField(fieldName);
+    if (!value.isAhatInstance()) {
+      return null;
+    }
+    return value.asAhatInstance().asByteArray();
+  }
+
+  public BufferedImage asBitmap() {
+    if (!isInstanceOfClass("android.graphics.Bitmap")) {
+      return null;
+    }
+
+    Integer width = getIntField("mWidth", null);
+    if (width == null) {
+      return null;
+    }
+
+    Integer height = getIntField("mHeight", null);
+    if (height == null) {
+      return null;
+    }
+
+    byte[] buffer = getByteArrayField("mBuffer");
+    if (buffer == null) {
+      return null;
+    }
+
+    // Convert the raw data to an image
+    // Convert BGRA to ABGR
+    int[] abgr = new int[height * width];
+    for (int i = 0; i < abgr.length; i++) {
+      abgr[i] = (
+          (((int) buffer[i * 4 + 3] & 0xFF) << 24)
+          + (((int) buffer[i * 4 + 0] & 0xFF) << 16)
+          + (((int) buffer[i * 4 + 1] & 0xFF) << 8)
+          + ((int) buffer[i * 4 + 2] & 0xFF));
+    }
+
+    BufferedImage bitmap = new BufferedImage(
+        width, height, BufferedImage.TYPE_4BYTE_ABGR);
+    bitmap.setRGB(0, 0, width, height, abgr, 0, width);
+    return bitmap;
+  }
+}
diff --git a/tools/ahat/src/heapdump/AhatClassObj.java b/tools/ahat/src/heapdump/AhatClassObj.java
new file mode 100644
index 0000000..c5ade1d
--- /dev/null
+++ b/tools/ahat/src/heapdump/AhatClassObj.java
@@ -0,0 +1,115 @@
+/*
+ * 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.
+ */
+
+package com.android.ahat.heapdump;
+
+import com.android.tools.perflib.heap.ClassObj;
+import com.android.tools.perflib.heap.Field;
+import com.android.tools.perflib.heap.Instance;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+public class AhatClassObj extends AhatInstance {
+  private String mClassName;
+  private AhatClassObj mSuperClassObj;
+  private AhatInstance mClassLoader;
+  private FieldValue[] mStaticFieldValues;
+
+  public AhatClassObj(long id) {
+    super(id);
+  }
+
+  @Override void initialize(AhatSnapshot snapshot, Instance inst) {
+    super.initialize(snapshot, inst);
+
+    ClassObj classObj = (ClassObj)inst;
+    mClassName = classObj.getClassName();
+
+    ClassObj superClassObj = classObj.getSuperClassObj();
+    if (superClassObj != null) {
+      mSuperClassObj = snapshot.findClassObj(superClassObj.getId());
+    }
+
+    Instance loader = classObj.getClassLoader();
+    if (loader != null) {
+      mClassLoader = snapshot.findInstance(loader.getId());
+    }
+
+    Collection<Map.Entry<Field, Object>> fieldValues = classObj.getStaticFieldValues().entrySet();
+    mStaticFieldValues = new FieldValue[fieldValues.size()];
+    int index = 0;
+    for (Map.Entry<Field, Object> field : fieldValues) {
+      String name = field.getKey().getName();
+      String type = field.getKey().getType().toString();
+      Value value = snapshot.getValue(field.getValue());
+      mStaticFieldValues[index++] = new FieldValue(name, type, value);
+
+      if (field.getValue() instanceof Instance) {
+        Instance ref = (Instance)field.getValue();
+        if (ref.getNextInstanceToGcRoot() == inst) {
+          value.asAhatInstance().setNextInstanceToGcRoot(this, "." + name);
+        }
+      }
+    }
+  }
+
+  /**
+   * Returns the name of the class this is a class object for.
+   */
+  public String getName() {
+    return mClassName;
+  }
+
+  /**
+   * Returns the superclass of this class object.
+   */
+  public AhatClassObj getSuperClassObj() {
+    return mSuperClassObj;
+  }
+
+  /**
+   * Returns the class loader of this class object.
+   */
+  public AhatInstance getClassLoader() {
+    return mClassLoader;
+  }
+
+  /**
+   * Returns the static field values for this class object.
+   */
+  public List<FieldValue> getStaticFieldValues() {
+    return Arrays.asList(mStaticFieldValues);
+  }
+
+  @Override public boolean isClassObj() {
+    return true;
+  }
+
+  @Override public AhatClassObj asClassObj() {
+    return this;
+  }
+
+  @Override public String toString() {
+    return mClassName;
+  }
+
+  @Override AhatInstance newPlaceHolderInstance() {
+    return new AhatPlaceHolderClassObj(this);
+  }
+}
+
diff --git a/tools/ahat/src/heapdump/AhatField.java b/tools/ahat/src/heapdump/AhatField.java
new file mode 100644
index 0000000..a25ee28
--- /dev/null
+++ b/tools/ahat/src/heapdump/AhatField.java
@@ -0,0 +1,42 @@
+/*
+ * 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.
+ */
+
+package com.android.ahat.heapdump;
+
+public class AhatField {
+  private final String mName;
+  private final String mType;
+
+  public AhatField(String name, String type) {
+    mName = name;
+    mType = type;
+  }
+
+  /**
+   * Returns the name of the field.
+   */
+  public String getName() {
+    return mName;
+  }
+
+  /**
+   * Returns a description of the type of the field.
+   */
+  public String getType() {
+    return mType;
+  }
+}
+
diff --git a/tools/ahat/src/heapdump/AhatHeap.java b/tools/ahat/src/heapdump/AhatHeap.java
new file mode 100644
index 0000000..c39adc4
--- /dev/null
+++ b/tools/ahat/src/heapdump/AhatHeap.java
@@ -0,0 +1,89 @@
+/*
+ * 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.
+ */
+
+package com.android.ahat.heapdump;
+
+public class AhatHeap implements Diffable<AhatHeap> {
+  private String mName;
+  private long mSize = 0;
+  private int mIndex;
+  private AhatHeap mBaseline;
+  private boolean mIsPlaceHolder = false;
+
+  AhatHeap(String name, int index) {
+    mName = name;
+    mIndex = index;
+    mBaseline = this;
+  }
+
+  /**
+   * Construct a place holder heap.
+   */
+  private AhatHeap(String name, AhatHeap baseline) {
+    mName = name;
+    mIndex = -1;
+    mBaseline = baseline;
+    baseline.setBaseline(this);
+    mIsPlaceHolder = true;
+  }
+
+  /**
+   * Construct a new place holder heap that has the given baseline heap.
+   */
+  static AhatHeap newPlaceHolderHeap(String name, AhatHeap baseline) {
+    return new AhatHeap(name, baseline);
+  }
+
+  void addToSize(long increment) {
+    mSize += increment;
+  }
+
+  /**
+   * Returns a unique instance for this heap between 0 and the total number of
+   * heaps in this snapshot, or -1 if this is a placeholder heap.
+   */
+  int getIndex() {
+    return mIndex;
+  }
+
+  /**
+   * Returns the name of this heap.
+   */
+  public String getName() {
+    return mName;
+  }
+
+  /**
+   * Returns the total number of bytes allocated on this heap.
+   */
+  public long getSize() {
+    return mSize;
+  }
+
+  void setBaseline(AhatHeap baseline) {
+    mBaseline = baseline;
+  }
+
+  @Override
+  public AhatHeap getBaseline() {
+    return mBaseline;
+  }
+
+  @Override
+  public boolean isPlaceHolder() {
+    return mIsPlaceHolder;
+  }
+}
diff --git a/tools/ahat/src/heapdump/AhatInstance.java b/tools/ahat/src/heapdump/AhatInstance.java
new file mode 100644
index 0000000..e6b9c00
--- /dev/null
+++ b/tools/ahat/src/heapdump/AhatInstance.java
@@ -0,0 +1,455 @@
+/*
+ * 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.
+ */
+
+package com.android.ahat.heapdump;
+
+import com.android.tools.perflib.heap.ClassObj;
+import com.android.tools.perflib.heap.Instance;
+import com.android.tools.perflib.heap.RootObj;
+import java.awt.image.BufferedImage;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+public abstract class AhatInstance implements Diffable<AhatInstance> {
+  private long mId;
+  private long mSize;
+  private long mTotalRetainedSize;
+  private long mRetainedSizes[];      // Retained size indexed by heap index
+  private boolean mIsReachable;
+  private AhatHeap mHeap;
+  private AhatInstance mImmediateDominator;
+  private AhatInstance mNextInstanceToGcRoot;
+  private String mNextInstanceToGcRootField = "???";
+  private AhatClassObj mClassObj;
+  private AhatInstance[] mHardReverseReferences;
+  private AhatInstance[] mSoftReverseReferences;
+  private Site mSite;
+
+  // If this instance is a root, mRootTypes contains a set of the root types.
+  // If this instance is not a root, mRootTypes is null.
+  private List<String> mRootTypes;
+
+  // List of instances this instance immediately dominates.
+  private List<AhatInstance> mDominated = new ArrayList<AhatInstance>();
+
+  private AhatInstance mBaseline;
+
+  public AhatInstance(long id) {
+    mId = id;
+    mBaseline = this;
+  }
+
+  /**
+   * Initializes this AhatInstance based on the given perflib instance.
+   * The AhatSnapshot should be used to look up AhatInstances and AhatHeaps.
+   * There is no guarantee that the AhatInstances returned by
+   * snapshot.findInstance have been initialized yet.
+   */
+  void initialize(AhatSnapshot snapshot, Instance inst) {
+    mId = inst.getId();
+    mSize = inst.getSize();
+    mTotalRetainedSize = inst.getTotalRetainedSize();
+    mIsReachable = inst.isReachable();
+
+    List<AhatHeap> heaps = snapshot.getHeaps();
+    mRetainedSizes = new long[heaps.size()];
+    for (AhatHeap heap : heaps) {
+      mRetainedSizes[heap.getIndex()] = inst.getRetainedSize(heap.getIndex());
+    }
+
+    mHeap = snapshot.getHeap(inst.getHeap().getName());
+
+    Instance dom = inst.getImmediateDominator();
+    if (dom == null || dom instanceof RootObj) {
+      mImmediateDominator = null;
+    } else {
+      mImmediateDominator = snapshot.findInstance(dom.getId());
+      mImmediateDominator.mDominated.add(this);
+    }
+
+    ClassObj clsObj = inst.getClassObj();
+    if (clsObj != null) {
+      mClassObj = snapshot.findClassObj(clsObj.getId());
+    }
+
+    // A couple notes about reverse references:
+    // * perflib sometimes returns unreachable reverse references. If
+    //   snapshot.findInstance returns null, it means the reverse reference is
+    //   not reachable, so we filter it out.
+    // * We store the references as AhatInstance[] instead of
+    //   ArrayList<AhatInstance> because it saves a lot of space and helps
+    //   with performance when there are a lot of AhatInstances.
+    ArrayList<AhatInstance> ahatRefs = new ArrayList<AhatInstance>();
+    ahatRefs = new ArrayList<AhatInstance>();
+    for (Instance ref : inst.getHardReverseReferences()) {
+      AhatInstance ahat = snapshot.findInstance(ref.getId());
+      if (ahat != null) {
+        ahatRefs.add(ahat);
+      }
+    }
+    mHardReverseReferences = new AhatInstance[ahatRefs.size()];
+    ahatRefs.toArray(mHardReverseReferences);
+
+    List<Instance> refs = inst.getSoftReverseReferences();
+    ahatRefs.clear();
+    if (refs != null) {
+      for (Instance ref : refs) {
+        AhatInstance ahat = snapshot.findInstance(ref.getId());
+        if (ahat != null) {
+          ahatRefs.add(ahat);
+        }
+      }
+    }
+    mSoftReverseReferences = new AhatInstance[ahatRefs.size()];
+    ahatRefs.toArray(mSoftReverseReferences);
+  }
+
+  /**
+   * Returns a unique identifier for the instance.
+   */
+  public long getId() {
+    return mId;
+  }
+
+  /**
+   * Returns the shallow number of bytes this object takes up.
+   */
+  public long getSize() {
+    return mSize;
+  }
+
+  /**
+   * Returns the number of bytes belonging to the given heap that this instance
+   * retains.
+   */
+  public long getRetainedSize(AhatHeap heap) {
+    int index = heap.getIndex();
+    return 0 <= index && index < mRetainedSizes.length ? mRetainedSizes[heap.getIndex()] : 0;
+  }
+
+  /**
+   * Returns the total number of bytes this instance retains.
+   */
+  public long getTotalRetainedSize() {
+    return mTotalRetainedSize;
+  }
+
+  /**
+   * Returns whether this object is strongly-reachable.
+   */
+  public boolean isReachable() {
+    return mIsReachable;
+  }
+
+  /**
+   * Returns the heap that this instance is allocated on.
+   */
+  public AhatHeap getHeap() {
+    return mHeap;
+  }
+
+  /**
+   * Returns true if this instance is marked as a root instance.
+   */
+  public boolean isRoot() {
+    return mRootTypes != null;
+  }
+
+  /**
+   * Marks this instance as being a root of the given type.
+   */
+  void addRootType(String type) {
+    if (mRootTypes == null) {
+      mRootTypes = new ArrayList<String>();
+      mRootTypes.add(type);
+    } else if (!mRootTypes.contains(type)) {
+      mRootTypes.add(type);
+    }
+  }
+
+  /**
+   * Returns a list of string descriptions of the root types of this object.
+   * Returns null if this object is not a root.
+   */
+  public Collection<String> getRootTypes() {
+    return mRootTypes;
+  }
+
+  /**
+   * Returns the immediate dominator of this instance.
+   * Returns null if this is a root instance.
+   */
+  public AhatInstance getImmediateDominator() {
+    return mImmediateDominator;
+  }
+
+  /**
+   * Returns a list of those objects immediately dominated by the given
+   * instance.
+   */
+  public List<AhatInstance> getDominated() {
+    return mDominated;
+  }
+
+  /**
+   * Returns the site where this instance was allocated.
+   */
+  public Site getSite() {
+    return mSite;
+  }
+
+  /**
+   * Sets the allocation site of this instance.
+   */
+  void setSite(Site site) {
+    mSite = site;
+  }
+
+  /**
+   * Returns true if the given instance is a class object
+   */
+  public boolean isClassObj() {
+    // Overridden by AhatClassObj.
+    return false;
+  }
+
+  /**
+   * Returns this as an AhatClassObj if this is an AhatClassObj.
+   * Returns null if this is not an AhatClassObj.
+   */
+  public AhatClassObj asClassObj() {
+    // Overridden by AhatClassObj.
+    return null;
+  }
+
+  /**
+   * Returns the class object instance for the class of this object.
+   */
+  public AhatClassObj getClassObj() {
+    return mClassObj;
+  }
+
+  /**
+   * Returns the name of the class this object belongs to.
+   */
+  public String getClassName() {
+    AhatClassObj classObj = getClassObj();
+    return classObj == null ? "???" : classObj.getName();
+  }
+
+  /**
+   * Returns true if the given instance is an array instance
+   */
+  public boolean isArrayInstance() {
+    // Overridden by AhatArrayInstance.
+    return false;
+  }
+
+  /**
+   * Returns this as an AhatArrayInstance if this is an AhatArrayInstance.
+   * Returns null if this is not an AhatArrayInstance.
+   */
+  public AhatArrayInstance asArrayInstance() {
+    // Overridden by AhatArrayInstance.
+    return null;
+  }
+
+  /**
+   * Returns true if the given instance is a class instance
+   */
+  public boolean isClassInstance() {
+    return false;
+  }
+
+  /**
+   * Returns this as an AhatClassInstance if this is an AhatClassInstance.
+   * Returns null if this is not an AhatClassInstance.
+   */
+  public AhatClassInstance asClassInstance() {
+    return null;
+  }
+
+  /**
+   * Return the referent associated with this instance.
+   * This is relevent for instances of java.lang.ref.Reference.
+   * Returns null if the instance has no referent associated with it.
+   */
+  public AhatInstance getReferent() {
+    // Overridden by AhatClassInstance.
+    return null;
+  }
+
+  /**
+   * Returns a list of objects with hard references to this object.
+   */
+  public List<AhatInstance> getHardReverseReferences() {
+    return Arrays.asList(mHardReverseReferences);
+  }
+
+  /**
+   * Returns a list of objects with soft references to this object.
+   */
+  public List<AhatInstance> getSoftReverseReferences() {
+    return Arrays.asList(mSoftReverseReferences);
+  }
+
+  /**
+   * Returns the value of a field of an instance.
+   * Returns null if the field value is null, the field couldn't be read, or
+   * there are multiple fields with the same name.
+   */
+  public Value getField(String fieldName) {
+    // Overridden by AhatClassInstance.
+    return null;
+  }
+
+  /**
+   * Reads a reference field of this instance.
+   * Returns null if the field value is null, or if the field couldn't be read.
+   */
+  public AhatInstance getRefField(String fieldName) {
+    // Overridden by AhatClassInstance.
+    return null;
+  }
+
+  /**
+   * Assuming inst represents a DexCache object, return the dex location for
+   * that dex cache. Returns null if the given instance doesn't represent a
+   * DexCache object or the location could not be found.
+   * If maxChars is non-negative, the returned location is truncated to
+   * maxChars in length.
+   */
+  public String getDexCacheLocation(int maxChars) {
+    return null;
+  }
+
+  /**
+   * Return the bitmap instance associated with this object, or null if there
+   * is none. This works for android.graphics.Bitmap instances and their
+   * underlying Byte[] instances.
+   */
+  public AhatInstance getAssociatedBitmapInstance() {
+    return null;
+  }
+
+  /**
+   * Read the string value from this instance.
+   * Returns null if this object can't be interpreted as a string.
+   * The returned string is truncated to maxChars characters.
+   * If maxChars is negative, the returned string is not truncated.
+   */
+  public String asString(int maxChars) {
+    // By default instances can't be interpreted as a string. This method is
+    // overridden by AhatClassInstance and AhatArrayInstance for those cases
+    // when an instance can be interpreted as a string.
+    return null;
+  }
+
+  /**
+   * Reads the string value from an hprof Instance.
+   * Returns null if the object can't be interpreted as a string.
+   */
+  public String asString() {
+    return asString(-1);
+  }
+
+  /**
+   * Return the bitmap associated with the given instance, if any.
+   * This is relevant for instances of android.graphics.Bitmap and byte[].
+   * Returns null if there is no bitmap associated with the given instance.
+   */
+  public BufferedImage asBitmap() {
+    return null;
+  }
+
+  /**
+   * Returns a sample path from a GC root to this instance.
+   * This instance is included as the last element of the path with an empty
+   * field description.
+   */
+  public List<PathElement> getPathFromGcRoot() {
+    List<PathElement> path = new ArrayList<PathElement>();
+
+    AhatInstance dom = this;
+    for (PathElement elem = new PathElement(this, ""); elem != null;
+        elem = getNextPathElementToGcRoot(elem.instance)) {
+      if (elem.instance.equals(dom)) {
+        elem.isDominator = true;
+        dom = dom.getImmediateDominator();
+      }
+      path.add(elem);
+    }
+    Collections.reverse(path);
+    return path;
+  }
+
+  /**
+   * Returns the next instance to GC root from this object and a string
+   * description of which field of that object refers to the given instance.
+   * Returns null if the given instance has no next instance to the gc root.
+   */
+  private static PathElement getNextPathElementToGcRoot(AhatInstance inst) {
+    AhatInstance parent = inst.mNextInstanceToGcRoot;
+    if (parent == null) {
+      return null;
+    }
+    return new PathElement(inst.mNextInstanceToGcRoot, inst.mNextInstanceToGcRootField);
+  }
+
+  void setNextInstanceToGcRoot(AhatInstance inst, String field) {
+    mNextInstanceToGcRoot = inst;
+    mNextInstanceToGcRootField = field;
+  }
+
+  /** Returns a human-readable identifier for this object.
+   * For class objects, the string is the class name.
+   * For class instances, the string is the class name followed by '@' and the
+   * hex id of the instance.
+   * For array instances, the string is the array type followed by the size in
+   * square brackets, followed by '@' and the hex id of the instance.
+   */
+  @Override public abstract String toString();
+
+  /**
+   * Read the byte[] value from an hprof Instance.
+   * Returns null if the instance is not a byte array.
+   */
+  byte[] asByteArray() {
+    return null;
+  }
+
+  public void setBaseline(AhatInstance baseline) {
+    mBaseline = baseline;
+  }
+
+  @Override public AhatInstance getBaseline() {
+    return mBaseline;
+  }
+
+  @Override public boolean isPlaceHolder() {
+    return false;
+  }
+
+  /**
+   * Returns a new place holder instance corresponding to this instance.
+   */
+  AhatInstance newPlaceHolderInstance() {
+    return new AhatPlaceHolderInstance(this);
+  }
+}
diff --git a/tools/ahat/src/heapdump/AhatPlaceHolderClassObj.java b/tools/ahat/src/heapdump/AhatPlaceHolderClassObj.java
new file mode 100644
index 0000000..c6ad87f
--- /dev/null
+++ b/tools/ahat/src/heapdump/AhatPlaceHolderClassObj.java
@@ -0,0 +1,71 @@
+/*
+ * 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.
+ */
+
+package com.android.ahat.heapdump;
+
+/**
+ * PlaceHolder instance to take the place of a real AhatClassObj for
+ * the purposes of displaying diffs.
+ *
+ * This should be created through a call to newPlaceHolder();
+ */
+public class AhatPlaceHolderClassObj extends AhatClassObj {
+  AhatPlaceHolderClassObj(AhatClassObj baseline) {
+    super(-1);
+    setBaseline(baseline);
+    baseline.setBaseline(this);
+  }
+
+  @Override public long getSize() {
+    return 0;
+  }
+
+  @Override public long getRetainedSize(AhatHeap heap) {
+    return 0;
+  }
+
+  @Override public long getTotalRetainedSize() {
+    return 0;
+  }
+
+  @Override public AhatHeap getHeap() {
+    return getBaseline().getHeap().getBaseline();
+  }
+
+  @Override public String getClassName() {
+    return getBaseline().getClassName();
+  }
+
+  @Override public String toString() {
+    return getBaseline().toString();
+  }
+
+  @Override public boolean isPlaceHolder() {
+    return true;
+  }
+
+  @Override public String getName() {
+    return getBaseline().asClassObj().getName();
+  }
+
+  @Override public AhatClassObj getSuperClassObj() {
+    return getBaseline().asClassObj().getSuperClassObj().getBaseline().asClassObj();
+  }
+
+  @Override public AhatInstance getClassLoader() {
+    return getBaseline().asClassObj().getClassLoader().getBaseline();
+  }
+}
diff --git a/tools/ahat/src/heapdump/AhatPlaceHolderInstance.java b/tools/ahat/src/heapdump/AhatPlaceHolderInstance.java
new file mode 100644
index 0000000..9412eae
--- /dev/null
+++ b/tools/ahat/src/heapdump/AhatPlaceHolderInstance.java
@@ -0,0 +1,63 @@
+/*
+ * 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.
+ */
+
+package com.android.ahat.heapdump;
+
+/**
+ * Generic PlaceHolder instance to take the place of a real AhatInstance for
+ * the purposes of displaying diffs.
+ *
+ * This should be created through a call to AhatInstance.newPlaceHolder();
+ */
+public class AhatPlaceHolderInstance extends AhatInstance {
+  AhatPlaceHolderInstance(AhatInstance baseline) {
+    super(-1);
+    setBaseline(baseline);
+    baseline.setBaseline(this);
+  }
+
+  @Override public long getSize() {
+    return 0;
+  }
+
+  @Override public long getRetainedSize(AhatHeap heap) {
+    return 0;
+  }
+
+  @Override public long getTotalRetainedSize() {
+    return 0;
+  }
+
+  @Override public AhatHeap getHeap() {
+    return getBaseline().getHeap().getBaseline();
+  }
+
+  @Override public String getClassName() {
+    return getBaseline().getClassName();
+  }
+
+  @Override public String asString(int maxChars) {
+    return getBaseline().asString(maxChars);
+  }
+
+  @Override public String toString() {
+    return getBaseline().toString();
+  }
+
+  @Override public boolean isPlaceHolder() {
+    return true;
+  }
+}
diff --git a/tools/ahat/src/heapdump/AhatSnapshot.java b/tools/ahat/src/heapdump/AhatSnapshot.java
new file mode 100644
index 0000000..20b85da
--- /dev/null
+++ b/tools/ahat/src/heapdump/AhatSnapshot.java
@@ -0,0 +1,293 @@
+/*
+ * 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.
+ */
+
+package com.android.ahat.heapdump;
+
+import com.android.tools.perflib.captures.DataBuffer;
+import com.android.tools.perflib.captures.MemoryMappedFileBuffer;
+import com.android.tools.perflib.heap.ArrayInstance;
+import com.android.tools.perflib.heap.ClassInstance;
+import com.android.tools.perflib.heap.ClassObj;
+import com.android.tools.perflib.heap.Heap;
+import com.android.tools.perflib.heap.Instance;
+import com.android.tools.perflib.heap.ProguardMap;
+import com.android.tools.perflib.heap.RootObj;
+import com.android.tools.perflib.heap.Snapshot;
+import com.android.tools.perflib.heap.StackFrame;
+import com.android.tools.perflib.heap.StackTrace;
+import gnu.trove.TObjectProcedure;
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class AhatSnapshot implements Diffable<AhatSnapshot> {
+  private final Site mRootSite = new Site("ROOT");
+
+  // Collection of objects whose immediate dominator is the SENTINEL_ROOT.
+  private final List<AhatInstance> mRooted = new ArrayList<AhatInstance>();
+
+  // List of all ahat instances stored in increasing order by id.
+  private final List<AhatInstance> mInstances = new ArrayList<AhatInstance>();
+
+  // Map from class name to class object.
+  private final Map<String, AhatClassObj> mClasses = new HashMap<String, AhatClassObj>();
+
+  private final List<AhatHeap> mHeaps = new ArrayList<AhatHeap>();
+
+  private AhatSnapshot mBaseline = this;
+
+  /**
+   * Create an AhatSnapshot from an hprof file.
+   */
+  public static AhatSnapshot fromHprof(File hprof, ProguardMap map) throws IOException {
+    return fromDataBuffer(new MemoryMappedFileBuffer(hprof), map);
+  }
+
+  /**
+   * Create an AhatSnapshot from an in-memory data buffer.
+   */
+  public static AhatSnapshot fromDataBuffer(DataBuffer buffer, ProguardMap map) throws IOException {
+    AhatSnapshot snapshot = new AhatSnapshot(buffer, map);
+
+    // Request a GC now to clean up memory used by perflib. This helps to
+    // avoid a noticable pause when visiting the first interesting page in
+    // ahat.
+    System.gc();
+
+    return snapshot;
+  }
+
+  /**
+   * Constructs an AhatSnapshot for the given hprof binary data.
+   */
+  private AhatSnapshot(DataBuffer buffer, ProguardMap map) throws IOException {
+    Snapshot snapshot = Snapshot.createSnapshot(buffer, map);
+    snapshot.computeDominators();
+
+    // Properly label the class of class objects in the perflib snapshot, and
+    // count the total number of instances.
+    final ClassObj javaLangClass = snapshot.findClass("java.lang.Class");
+    if (javaLangClass != null) {
+      for (Heap heap : snapshot.getHeaps()) {
+        Collection<ClassObj> classes = heap.getClasses();
+        for (ClassObj clsObj : classes) {
+          if (clsObj.getClassObj() == null) {
+            clsObj.setClassId(javaLangClass.getId());
+          }
+        }
+      }
+    }
+
+    // Create mappings from id to ahat instance and heaps.
+    Collection<Heap> heaps = snapshot.getHeaps();
+    for (Heap heap : heaps) {
+      // Note: mHeaps will not be in index order if snapshot.getHeaps does not
+      // return heaps in index order. That's fine, because we don't rely on
+      // mHeaps being in index order.
+      mHeaps.add(new AhatHeap(heap.getName(), snapshot.getHeapIndex(heap)));
+      TObjectProcedure<Instance> doCreate = new TObjectProcedure<Instance>() {
+        @Override
+        public boolean execute(Instance inst) {
+          long id = inst.getId();
+          if (inst instanceof ClassInstance) {
+            mInstances.add(new AhatClassInstance(id));
+          } else if (inst instanceof ArrayInstance) {
+            mInstances.add(new AhatArrayInstance(id));
+          } else if (inst instanceof ClassObj) {
+            AhatClassObj classObj = new AhatClassObj(id);
+            mInstances.add(classObj);
+            mClasses.put(((ClassObj)inst).getClassName(), classObj);
+          }
+          return true;
+        }
+      };
+      for (Instance instance : heap.getClasses()) {
+        doCreate.execute(instance);
+      }
+      heap.forEachInstance(doCreate);
+    }
+
+    // Sort the instances by id so we can use binary search to lookup
+    // instances by id.
+    mInstances.sort(new Comparator<AhatInstance>() {
+      @Override
+      public int compare(AhatInstance a, AhatInstance b) {
+        return Long.compare(a.getId(), b.getId());
+      }
+    });
+
+    // Initialize ahat snapshot and instances based on the perflib snapshot
+    // and instances.
+    for (AhatInstance ahat : mInstances) {
+      Instance inst = snapshot.findInstance(ahat.getId());
+      ahat.initialize(this, inst);
+
+      if (inst.getImmediateDominator() == Snapshot.SENTINEL_ROOT) {
+        mRooted.add(ahat);
+      }
+
+      if (inst.isReachable()) {
+        ahat.getHeap().addToSize(ahat.getSize());
+      }
+
+      // Update sites.
+      StackFrame[] frames = null;
+      StackTrace stack = inst.getStack();
+      if (stack != null) {
+        frames = stack.getFrames();
+      }
+      Site site = mRootSite.add(frames, frames == null ? 0 : frames.length, ahat);
+      ahat.setSite(site);
+    }
+
+    // Record the roots and their types.
+    for (RootObj root : snapshot.getGCRoots()) {
+      Instance inst = root.getReferredInstance();
+      if (inst != null) {
+        findInstance(inst.getId()).addRootType(root.getRootType().toString());
+      }
+    }
+    snapshot.dispose();
+  }
+
+  /**
+   * Returns the instance with given id in this snapshot.
+   * Returns null if no instance with the given id is found.
+   */
+  public AhatInstance findInstance(long id) {
+    // Binary search over the sorted instances.
+    int start = 0;
+    int end = mInstances.size();
+    while (start < end) {
+      int mid = start + ((end - start) / 2);
+      AhatInstance midInst = mInstances.get(mid);
+      long midId = midInst.getId();
+      if (id == midId) {
+        return midInst;
+      } else if (id < midId) {
+        end = mid;
+      } else {
+        start = mid + 1;
+      }
+    }
+    return null;
+  }
+
+  /**
+   * Returns the AhatClassObj with given id in this snapshot.
+   * Returns null if no class object with the given id is found.
+   */
+  public AhatClassObj findClassObj(long id) {
+    AhatInstance inst = findInstance(id);
+    return inst == null ? null : inst.asClassObj();
+  }
+
+  /**
+   * Returns the class object for the class with given name.
+   * Returns null if there is no class object for the given name.
+   * Note: This method is exposed for testing purposes.
+   */
+  public AhatClassObj findClass(String name) {
+    return mClasses.get(name);
+  }
+
+  /**
+   * Returns the heap with the given name, if any.
+   * Returns null if no heap with the given name could be found.
+   */
+  public AhatHeap getHeap(String name) {
+    // We expect a small number of heaps (maybe 3 or 4 total), so a linear
+    // search should be acceptable here performance wise.
+    for (AhatHeap heap : getHeaps()) {
+      if (heap.getName().equals(name)) {
+        return heap;
+      }
+    }
+    return null;
+  }
+
+  /**
+   * Returns a list of heaps in the snapshot in canonical order.
+   * Modifications to the returned list are visible to this AhatSnapshot,
+   * which is used by diff to insert place holder heaps.
+   */
+  public List<AhatHeap> getHeaps() {
+    return mHeaps;
+  }
+
+  /**
+   * Returns a collection of instances whose immediate dominator is the
+   * SENTINEL_ROOT.
+   */
+  public List<AhatInstance> getRooted() {
+    return mRooted;
+  }
+
+  /**
+   * Returns the root site for this snapshot.
+   */
+  public Site getRootSite() {
+    return mRootSite;
+  }
+
+  // Get the site associated with the given id and depth.
+  // Returns the root site if no such site found.
+  public Site getSite(int id, int depth) {
+    AhatInstance obj = findInstance(id);
+    if (obj == null) {
+      return mRootSite;
+    }
+
+    Site site = obj.getSite();
+    for (int i = 0; i < depth && site.getParent() != null; i++) {
+      site = site.getParent();
+    }
+    return site;
+  }
+
+  // Return the Value for the given perflib value object.
+  Value getValue(Object value) {
+    if (value instanceof Instance) {
+      value = findInstance(((Instance)value).getId());
+    }
+    return value == null ? null : new Value(value);
+  }
+
+  public void setBaseline(AhatSnapshot baseline) {
+    mBaseline = baseline;
+  }
+
+  /**
+   * Returns true if this snapshot has been diffed against another, different
+   * snapshot.
+   */
+  public boolean isDiffed() {
+    return mBaseline != this;
+  }
+
+  @Override public AhatSnapshot getBaseline() {
+    return mBaseline;
+  }
+
+  @Override public boolean isPlaceHolder() {
+    return false;
+  }
+}
diff --git a/tools/ahat/src/heapdump/Diff.java b/tools/ahat/src/heapdump/Diff.java
new file mode 100644
index 0000000..943e6e6
--- /dev/null
+++ b/tools/ahat/src/heapdump/Diff.java
@@ -0,0 +1,383 @@
+/*
+ * 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.
+ */
+
+package com.android.ahat.heapdump;
+
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Deque;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+public class Diff {
+  /**
+   * Perform a diff between two heap lists.
+   *
+   * Heaps are diffed based on heap name. PlaceHolder heaps will be added to
+   * the given lists as necessary so that every heap in A has a corresponding
+   * heap in B and vice-versa.
+   */
+  private static void heaps(List<AhatHeap> a, List<AhatHeap> b) {
+    int asize = a.size();
+    int bsize = b.size();
+    for (int i = 0; i < bsize; i++) {
+      // Set the B heap's baseline as null to mark that we have not yet
+      // matched it with an A heap.
+      b.get(i).setBaseline(null);
+    }
+
+    for (int i = 0; i < asize; i++) {
+      AhatHeap aheap = a.get(i);
+      aheap.setBaseline(null);
+      for (int j = 0; j < bsize; j++) {
+        AhatHeap bheap = b.get(j);
+        if (bheap.getBaseline() == null && aheap.getName().equals(bheap.getName())) {
+          // We found a match between aheap and bheap.
+          aheap.setBaseline(bheap);
+          bheap.setBaseline(aheap);
+          break;
+        }
+      }
+
+      if (aheap.getBaseline() == null) {
+        // We did not find any match for aheap in snapshot B.
+        // Create a placeholder heap in snapshot B to use as the baseline.
+        b.add(AhatHeap.newPlaceHolderHeap(aheap.getName(), aheap));
+      }
+    }
+
+    // Make placeholder heaps in snapshot A for any unmatched heaps in
+    // snapshot B.
+    for (int i = 0; i < bsize; i++) {
+      AhatHeap bheap = b.get(i);
+      if (bheap.getBaseline() == null) {
+        a.add(AhatHeap.newPlaceHolderHeap(bheap.getName(), bheap));
+      }
+    }
+  }
+
+  /**
+   * Key represents an equivalence class of AhatInstances that are allowed to
+   * be considered for correspondence between two different snapshots.
+   */
+  private static class Key {
+    // Corresponding objects must belong to classes of the same name.
+    private final String mClass;
+
+    // Corresponding objects must belong to heaps of the same name.
+    private final String mHeapName;
+
+    // Corresponding string objects must have the same value.
+    // mStringValue is set to the empty string for non-string objects.
+    private final String mStringValue;
+
+    // Corresponding class objects must have the same class name.
+    // mClassName is set to the empty string for non-class objects.
+    private final String mClassName;
+
+    // Corresponding array objects must have the same length.
+    // mArrayLength is set to 0 for non-array objects.
+    private final int mArrayLength;
+
+
+    private Key(AhatInstance inst) {
+      mClass = inst.getClassName();
+      mHeapName = inst.getHeap().getName();
+      mClassName = inst.isClassObj() ? inst.asClassObj().getName() : "";
+      String string = inst.asString();
+      mStringValue = string == null ? "" : string;
+      AhatArrayInstance array = inst.asArrayInstance();
+      mArrayLength = array == null ? 0 : array.getLength();
+    }
+
+    /**
+     * Return the key for the given instance.
+     */
+    public static Key keyFor(AhatInstance inst) {
+      return new Key(inst);
+    }
+
+    @Override
+    public boolean equals(Object other) {
+      if (!(other instanceof Key)) {
+        return false;
+      }
+      Key o = (Key)other;
+      return mClass.equals(o.mClass)
+          && mHeapName.equals(o.mHeapName)
+          && mStringValue.equals(o.mStringValue)
+          && mClassName.equals(o.mClassName)
+          && mArrayLength == o.mArrayLength;
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hash(mClass, mHeapName, mStringValue, mClassName, mArrayLength);
+    }
+  }
+
+  private static class InstanceListPair {
+    public final List<AhatInstance> a;
+    public final List<AhatInstance> b;
+
+    public InstanceListPair() {
+      this.a = new ArrayList<AhatInstance>();
+      this.b = new ArrayList<AhatInstance>();
+    }
+
+    public InstanceListPair(List<AhatInstance> a, List<AhatInstance> b) {
+      this.a = a;
+      this.b = b;
+    }
+  }
+
+  /**
+   * Recursively create place holder instances for the given instance and
+   * every instance dominated by that instance.
+   * Returns the place holder instance created for the given instance.
+   * Adds all allocated placeholders to the given placeholders list.
+   */
+  private static AhatInstance createPlaceHolders(AhatInstance inst,
+      List<AhatInstance> placeholders) {
+    // Don't actually use recursion, because we could easily smash the stack.
+    // Instead we iterate.
+    AhatInstance result = inst.newPlaceHolderInstance();
+    placeholders.add(result);
+    Deque<AhatInstance> deque = new ArrayDeque<AhatInstance>();
+    deque.push(inst);
+    while (!deque.isEmpty()) {
+      inst = deque.pop();
+
+      for (AhatInstance child : inst.getDominated()) {
+        placeholders.add(child.newPlaceHolderInstance());
+        deque.push(child);
+      }
+    }
+    return result;
+  }
+
+  /**
+   * Recursively diff two dominator trees of instances.
+   * PlaceHolder objects are appended to the lists as needed to ensure every
+   * object has a corresponding baseline in the other list. All PlaceHolder
+   * objects are also appended to the given placeholders list, so their Site
+   * info can be updated later on.
+   */
+  private static void instances(List<AhatInstance> a, List<AhatInstance> b,
+      List<AhatInstance> placeholders) {
+    // Don't actually use recursion, because we could easily smash the stack.
+    // Instead we iterate.
+    Deque<InstanceListPair> deque = new ArrayDeque<InstanceListPair>();
+    deque.push(new InstanceListPair(a, b));
+    while (!deque.isEmpty()) {
+      InstanceListPair p = deque.pop();
+
+      // Group instances of the same equivalence class together.
+      Map<Key, InstanceListPair> byKey = new HashMap<Key, InstanceListPair>();
+      for (AhatInstance inst : p.a) {
+        Key key = Key.keyFor(inst);
+        InstanceListPair pair = byKey.get(key);
+        if (pair == null) {
+          pair = new InstanceListPair();
+          byKey.put(key, pair);
+        }
+        pair.a.add(inst);
+      }
+      for (AhatInstance inst : p.b) {
+        Key key = Key.keyFor(inst);
+        InstanceListPair pair = byKey.get(key);
+        if (pair == null) {
+          pair = new InstanceListPair();
+          byKey.put(key, pair);
+        }
+        pair.b.add(inst);
+      }
+
+      // diff objects from the same key class.
+      for (InstanceListPair pair : byKey.values()) {
+        // Sort by retained size and assume the elements at the top of the lists
+        // correspond to each other in that order. This could probably be
+        // improved if desired, but it gives good enough results for now.
+        Collections.sort(pair.a, Sort.INSTANCE_BY_TOTAL_RETAINED_SIZE);
+        Collections.sort(pair.b, Sort.INSTANCE_BY_TOTAL_RETAINED_SIZE);
+
+        int common = Math.min(pair.a.size(), pair.b.size());
+        for (int i = 0; i < common; i++) {
+          AhatInstance ainst = pair.a.get(i);
+          AhatInstance binst = pair.b.get(i);
+          ainst.setBaseline(binst);
+          binst.setBaseline(ainst);
+          deque.push(new InstanceListPair(ainst.getDominated(), binst.getDominated()));
+        }
+
+        // Add placeholder objects for anything leftover.
+        for (int i = common; i < pair.a.size(); i++) {
+          p.b.add(createPlaceHolders(pair.a.get(i), placeholders));
+        }
+
+        for (int i = common; i < pair.b.size(); i++) {
+          p.a.add(createPlaceHolders(pair.b.get(i), placeholders));
+        }
+      }
+    }
+  }
+
+  /**
+   * Sets the baseline for root and all its descendants to baseline.
+   */
+  private static void setSitesBaseline(Site root, Site baseline) {
+    root.setBaseline(baseline);
+    for (Site child : root.getChildren()) {
+      setSitesBaseline(child, baseline);
+    }
+  }
+
+  /**
+   * Recursively diff the two sites, setting them and their descendants as
+   * baselines for each other as appropriate.
+   *
+   * This requires that instances have already been diffed. In particular, we
+   * require all AhatClassObjs in one snapshot have corresponding (possibly
+   * place-holder) AhatClassObjs in the other snapshot.
+   */
+  private static void sites(Site a, Site b) {
+    // Set the sites as baselines of each other.
+    a.setBaseline(b);
+    b.setBaseline(a);
+
+    // Set the site's ObjectsInfos as baselines of each other. This implicitly
+    // adds new empty ObjectsInfo as needed.
+    for (Site.ObjectsInfo ainfo : a.getObjectsInfos()) {
+      AhatClassObj baseClassObj = null;
+      if (ainfo.classObj != null) {
+        baseClassObj = (AhatClassObj) ainfo.classObj.getBaseline();
+      }
+      ainfo.setBaseline(b.getObjectsInfo(ainfo.heap.getBaseline(), baseClassObj));
+    }
+    for (Site.ObjectsInfo binfo : b.getObjectsInfos()) {
+      AhatClassObj baseClassObj = null;
+      if (binfo.classObj != null) {
+        baseClassObj = (AhatClassObj) binfo.classObj.getBaseline();
+      }
+      binfo.setBaseline(a.getObjectsInfo(binfo.heap.getBaseline(), baseClassObj));
+    }
+
+    // Set B children's baselines as null to mark that we have not yet matched
+    // them with A children.
+    for (Site bchild : b.getChildren()) {
+      bchild.setBaseline(null);
+    }
+
+    for (Site achild : a.getChildren()) {
+      achild.setBaseline(null);
+      for (Site bchild : b.getChildren()) {
+        if (achild.getLineNumber() == bchild.getLineNumber()
+            && achild.getMethodName().equals(bchild.getMethodName())
+            && achild.getSignature().equals(bchild.getSignature())
+            && achild.getFilename().equals(bchild.getFilename())) {
+          // We found a match between achild and bchild.
+          sites(achild, bchild);
+          break;
+        }
+      }
+
+      if (achild.getBaseline() == null) {
+        // We did not find any match for achild in site B.
+        // Use B for the baseline of achild and its descendants.
+        setSitesBaseline(achild, b);
+      }
+    }
+
+    for (Site bchild : b.getChildren()) {
+      if (bchild.getBaseline() == null) {
+        setSitesBaseline(bchild, a);
+      }
+    }
+  }
+
+  /**
+   * Perform a diff of the two snapshots, setting each as the baseline for the
+   * other.
+   */
+  public static void snapshots(AhatSnapshot a, AhatSnapshot b) {
+    a.setBaseline(b);
+    b.setBaseline(a);
+
+    // Diff the heaps of each snapshot.
+    heaps(a.getHeaps(), b.getHeaps());
+
+    // Diff the instances of each snapshot.
+    List<AhatInstance> placeholders = new ArrayList<AhatInstance>();
+    instances(a.getRooted(), b.getRooted(), placeholders);
+
+    // Diff the sites of each snapshot.
+    // This requires the instances have already been diffed.
+    sites(a.getRootSite(), b.getRootSite());
+
+    // Add placeholders to their corresponding sites.
+    // This requires the sites have already been diffed.
+    for (AhatInstance placeholder : placeholders) {
+      placeholder.getBaseline().getSite().getBaseline().addPlaceHolderInstance(placeholder);
+    }
+  }
+
+  /**
+   * Diff two lists of field values.
+   * PlaceHolder objects are added to the given lists as needed to ensure
+   * every FieldValue in A ends up with a corresponding FieldValue in B.
+   */
+  public static void fields(List<FieldValue> a, List<FieldValue> b) {
+    // Fields with the same name and type are considered matching fields.
+    // For simplicity, we assume the matching fields are in the same order in
+    // both A and B, though some fields may be added or removed in either
+    // list. If our assumption is wrong, in the worst case the quality of the
+    // field diff is poor.
+
+    for (int i = 0; i < a.size(); i++) {
+      FieldValue afield = a.get(i);
+      afield.setBaseline(null);
+
+      // Find the matching field in B, if any.
+      for (int j = i; j < b.size(); j++) {
+        FieldValue bfield = b.get(j);
+        if (afield.getName().equals(bfield.getName())
+            && afield.getType().equals(bfield.getType())) {
+          // We found the matching field in B.
+          // Assume fields i, ..., j-1 in B have no match in A.
+          for ( ; i < j; i++) {
+            a.add(i, FieldValue.newPlaceHolderFieldValue(b.get(i)));
+          }
+
+          afield.setBaseline(bfield);
+          bfield.setBaseline(afield);
+          break;
+        }
+      }
+
+      if (afield.getBaseline() == null) {
+        b.add(i, FieldValue.newPlaceHolderFieldValue(afield));
+      }
+    }
+
+    // All remaining fields in B are unmatched by any in A.
+    for (int i = a.size(); i < b.size(); i++) {
+      a.add(i, FieldValue.newPlaceHolderFieldValue(b.get(i)));
+    }
+  }
+}
diff --git a/tools/ahat/src/heapdump/Diffable.java b/tools/ahat/src/heapdump/Diffable.java
new file mode 100644
index 0000000..53442c8
--- /dev/null
+++ b/tools/ahat/src/heapdump/Diffable.java
@@ -0,0 +1,38 @@
+/*
+ * 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.
+ */
+
+package com.android.ahat.heapdump;
+
+/**
+ * An interface for objects that have corresponding objects in a baseline heap
+ * dump.
+ */
+public interface Diffable<T> {
+  /**
+   * Return the baseline object that corresponds to this one.
+   */
+  T getBaseline();
+
+  /**
+   * Returns true if this is a placeholder object.
+   * A placeholder object is used to indicate there is some object in the
+   * baseline heap dump that is not in this heap dump. In that case, we create
+   * a dummy place holder object in this heap dump as an indicator of the
+   * object removed from the baseline heap dump.
+   */
+  boolean isPlaceHolder();
+}
+
diff --git a/tools/ahat/src/heapdump/FieldValue.java b/tools/ahat/src/heapdump/FieldValue.java
new file mode 100644
index 0000000..3f65cd3
--- /dev/null
+++ b/tools/ahat/src/heapdump/FieldValue.java
@@ -0,0 +1,83 @@
+/*
+ * 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.
+ */
+
+package com.android.ahat.heapdump;
+
+public class FieldValue implements Diffable<FieldValue> {
+  private final String mName;
+  private final String mType;
+  private final Value mValue;
+  private FieldValue mBaseline;
+  private final boolean mIsPlaceHolder;
+
+  public FieldValue(String name, String type, Value value) {
+    mName = name;
+    mType = type;
+    mValue = value;
+    mBaseline = this;
+    mIsPlaceHolder = false;
+  }
+
+  /**
+   * Construct a place holder FieldValue
+   */
+  private FieldValue(FieldValue baseline) {
+    mName = baseline.mName;
+    mType = baseline.mType;
+    mValue = Value.getBaseline(baseline.mValue);
+    mBaseline = baseline;
+    mIsPlaceHolder = true;
+  }
+
+  static FieldValue newPlaceHolderFieldValue(FieldValue baseline) {
+    FieldValue field = new FieldValue(baseline);
+    baseline.setBaseline(field);
+    return field;
+  }
+
+  /**
+   * Returns the name of the field.
+   */
+  public String getName() {
+    return mName;
+  }
+
+  /**
+   * Returns a description of the type of the field.
+   */
+  public String getType() {
+    return mType;
+  }
+
+  /**
+   * Returns the value of this field.
+   */
+  public Value getValue() {
+    return mValue;
+  }
+
+  public void setBaseline(FieldValue baseline) {
+    mBaseline = baseline;
+  }
+
+  @Override public FieldValue getBaseline() {
+    return mBaseline;
+  }
+
+  @Override public boolean isPlaceHolder() {
+    return mIsPlaceHolder;
+  }
+}
diff --git a/tools/ahat/src/heapdump/PathElement.java b/tools/ahat/src/heapdump/PathElement.java
new file mode 100644
index 0000000..196a246
--- /dev/null
+++ b/tools/ahat/src/heapdump/PathElement.java
@@ -0,0 +1,37 @@
+/*
+ * 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.
+ */
+
+package com.android.ahat.heapdump;
+
+public class PathElement implements Diffable<PathElement> {
+  public final AhatInstance instance;
+  public final String field;
+  public boolean isDominator;
+
+  public PathElement(AhatInstance instance, String field) {
+    this.instance = instance;
+    this.field = field;
+    this.isDominator = false;
+  }
+
+  @Override public PathElement getBaseline() {
+    return this;
+  }
+
+  @Override public boolean isPlaceHolder() {
+    return false;
+  }
+}
diff --git a/tools/ahat/src/heapdump/Site.java b/tools/ahat/src/heapdump/Site.java
new file mode 100644
index 0000000..738eaf0
--- /dev/null
+++ b/tools/ahat/src/heapdump/Site.java
@@ -0,0 +1,284 @@
+/*
+ * 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.
+ */
+
+package com.android.ahat.heapdump;
+
+import com.android.tools.perflib.heap.StackFrame;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class Site implements Diffable<Site> {
+  // The site that this site was directly called from.
+  // mParent is null for the root site.
+  private Site mParent;
+
+  private String mMethodName;
+  private String mSignature;
+  private String mFilename;
+  private int mLineNumber;
+
+  // To identify this site, we pick a stack trace that includes the site.
+  // mId is the id of an object allocated at that stack trace, and mDepth
+  // is the number of calls between this site and the innermost site of
+  // allocation of the object with mId.
+  // For the root site, mId is 0 and mDepth is 0.
+  private long mId;
+  private int mDepth;
+
+  // The total size of objects allocated in this site (including child sites),
+  // organized by heap index. Heap indices outside the range of mSizesByHeap
+  // implicitly have size 0.
+  private long[] mSizesByHeap;
+
+  // List of child sites.
+  private List<Site> mChildren;
+
+  // List of all objects allocated in this site (including child sites).
+  private List<AhatInstance> mObjects;
+  private List<ObjectsInfo> mObjectsInfos;
+  private Map<AhatHeap, Map<AhatClassObj, ObjectsInfo>> mObjectsInfoMap;
+
+  private Site mBaseline;
+
+  public static class ObjectsInfo implements Diffable<ObjectsInfo> {
+    public AhatHeap heap;
+    public AhatClassObj classObj;   // May be null.
+    public long numInstances;
+    public long numBytes;
+    private ObjectsInfo baseline;
+
+    public ObjectsInfo(AhatHeap heap, AhatClassObj classObj, long numInstances, long numBytes) {
+      this.heap = heap;
+      this.classObj = classObj;
+      this.numInstances = numInstances;
+      this.numBytes = numBytes;
+      this.baseline = this;
+    }
+
+    /**
+     * Returns the name of the class this ObjectsInfo is associated with.
+     */
+    public String getClassName() {
+      return classObj == null ? "???" : classObj.getName();
+    }
+
+    public void setBaseline(ObjectsInfo baseline) {
+      this.baseline = baseline;
+    }
+
+    @Override public ObjectsInfo getBaseline() {
+      return baseline;
+    }
+
+    @Override public boolean isPlaceHolder() {
+      return false;
+    }
+  }
+
+  /**
+   * Construct a root site.
+   */
+  public Site(String name) {
+    this(null, name, "", "", 0, 0, 0);
+  }
+
+  public Site(Site parent, String method, String signature, String file,
+      int line, long id, int depth) {
+    mParent = parent;
+    mMethodName = method;
+    mSignature = signature;
+    mFilename = file;
+    mLineNumber = line;
+    mId = id;
+    mDepth = depth;
+    mSizesByHeap = new long[1];
+    mChildren = new ArrayList<Site>();
+    mObjects = new ArrayList<AhatInstance>();
+    mObjectsInfos = new ArrayList<ObjectsInfo>();
+    mObjectsInfoMap = new HashMap<AhatHeap, Map<AhatClassObj, ObjectsInfo>>();
+    mBaseline = this;
+  }
+
+  /**
+   * Add an instance to this site.
+   * Returns the site at which the instance was allocated.
+   * @param frames - The list of frames in the stack trace, starting with the inner-most frame.
+   * @param depth - The number of frames remaining before the inner-most frame is reached.
+   */
+  Site add(StackFrame[] frames, int depth, AhatInstance inst) {
+    return add(this, frames, depth, inst);
+  }
+
+  private static Site add(Site site, StackFrame[] frames, int depth, AhatInstance inst) {
+    while (true) {
+      site.mObjects.add(inst);
+
+      ObjectsInfo info = site.getObjectsInfo(inst.getHeap(), inst.getClassObj());
+      if (inst.isReachable()) {
+        AhatHeap heap = inst.getHeap();
+        if (heap.getIndex() >= site.mSizesByHeap.length) {
+          long[] newSizes = new long[heap.getIndex() + 1];
+          for (int i = 0; i < site.mSizesByHeap.length; i++) {
+            newSizes[i] = site.mSizesByHeap[i];
+          }
+          site.mSizesByHeap = newSizes;
+        }
+        site.mSizesByHeap[heap.getIndex()] += inst.getSize();
+
+        info.numInstances++;
+        info.numBytes += inst.getSize();
+      }
+
+      if (depth > 0) {
+        StackFrame next = frames[depth - 1];
+        Site child = null;
+        for (int i = 0; i < site.mChildren.size(); i++) {
+          Site curr = site.mChildren.get(i);
+          if (curr.mLineNumber == next.getLineNumber()
+              && curr.mMethodName.equals(next.getMethodName())
+              && curr.mSignature.equals(next.getSignature())
+              && curr.mFilename.equals(next.getFilename())) {
+            child = curr;
+            break;
+          }
+        }
+        if (child == null) {
+          child = new Site(site, next.getMethodName(), next.getSignature(),
+              next.getFilename(), next.getLineNumber(), inst.getId(), depth - 1);
+          site.mChildren.add(child);
+        }
+        depth = depth - 1;
+        site = child;
+      } else {
+        return site;
+      }
+    }
+  }
+
+  // Get the size of a site for a specific heap.
+  public long getSize(AhatHeap heap) {
+    int index = heap.getIndex();
+    return index >= 0 && index < mSizesByHeap.length ? mSizesByHeap[index] : 0;
+  }
+
+  /**
+   * Get the list of objects allocated under this site. Includes objects
+   * allocated in children sites.
+   */
+  public Collection<AhatInstance> getObjects() {
+    return mObjects;
+  }
+
+  /**
+   * Returns the ObjectsInfo at this site for the given heap and class
+   * objects. Creates a new empty ObjectsInfo if none existed before.
+   */
+  ObjectsInfo getObjectsInfo(AhatHeap heap, AhatClassObj classObj) {
+    Map<AhatClassObj, ObjectsInfo> classToObjectsInfo = mObjectsInfoMap.get(heap);
+    if (classToObjectsInfo == null) {
+      classToObjectsInfo = new HashMap<AhatClassObj, ObjectsInfo>();
+      mObjectsInfoMap.put(heap, classToObjectsInfo);
+    }
+
+    ObjectsInfo info = classToObjectsInfo.get(classObj);
+    if (info == null) {
+      info = new ObjectsInfo(heap, classObj, 0, 0);
+      mObjectsInfos.add(info);
+      classToObjectsInfo.put(classObj, info);
+    }
+    return info;
+  }
+
+  public List<ObjectsInfo> getObjectsInfos() {
+    return mObjectsInfos;
+  }
+
+  // Get the combined size of the site for all heaps.
+  public long getTotalSize() {
+    long total = 0;
+    for (int i = 0; i < mSizesByHeap.length; i++) {
+      total += mSizesByHeap[i];
+    }
+    return total;
+  }
+
+  /**
+   * Return the site this site was called from.
+   * Returns null for the root site.
+   */
+  public Site getParent() {
+    return mParent;
+  }
+
+  public String getMethodName() {
+    return mMethodName;
+  }
+
+  public String getSignature() {
+    return mSignature;
+  }
+
+  public String getFilename() {
+    return mFilename;
+  }
+
+  public int getLineNumber() {
+    return mLineNumber;
+  }
+
+  /**
+   * Returns the id of some object allocated in this site.
+   */
+  public long getId() {
+    return mId;
+  }
+
+  /**
+   * Returns the number of frames between this site and the site where the
+   * object with id getId() was allocated.
+   */
+  public int getDepth() {
+    return mDepth;
+  }
+
+  public List<Site> getChildren() {
+    return mChildren;
+  }
+
+  void setBaseline(Site baseline) {
+    mBaseline = baseline;
+  }
+
+  @Override public Site getBaseline() {
+    return mBaseline;
+  }
+
+  @Override public boolean isPlaceHolder() {
+    return false;
+  }
+
+  /**
+   * Adds a place holder instance to this site and all parent sites.
+   */
+  void addPlaceHolderInstance(AhatInstance placeholder) {
+    for (Site site = this; site != null; site = site.mParent) {
+      site.mObjects.add(placeholder);
+    }
+  }
+}
diff --git a/tools/ahat/src/heapdump/Sort.java b/tools/ahat/src/heapdump/Sort.java
new file mode 100644
index 0000000..93d147a
--- /dev/null
+++ b/tools/ahat/src/heapdump/Sort.java
@@ -0,0 +1,193 @@
+/*
+ * 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.
+ */
+
+package com.android.ahat.heapdump;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Provides Comparators and helper functions for sorting Instances, Sites, and
+ * other things.
+ *
+ * Note: The Comparators defined here impose orderings that are inconsistent
+ * with equals. They should not be used for element lookup or search. They
+ * should only be used for showing elements to the user in different orders.
+ */
+public class Sort {
+  /**
+   * Compare instances by their total retained size.
+   * Different instances with the same total retained size are considered
+   * equal for the purposes of comparison.
+   * This sorts instances from larger retained size to smaller retained size.
+   */
+  public static final Comparator<AhatInstance> INSTANCE_BY_TOTAL_RETAINED_SIZE
+    = new Comparator<AhatInstance>() {
+    @Override
+    public int compare(AhatInstance a, AhatInstance b) {
+      return Long.compare(b.getTotalRetainedSize(), a.getTotalRetainedSize());
+    }
+  };
+
+  /**
+   * Compare instances by their retained size for a given heap index.
+   * Different instances with the same total retained size are considered
+   * equal for the purposes of comparison.
+   * This sorts instances from larger retained size to smaller retained size.
+   */
+  public static class InstanceByHeapRetainedSize implements Comparator<AhatInstance> {
+    private AhatHeap mHeap;
+
+    public InstanceByHeapRetainedSize(AhatHeap heap) {
+      mHeap = heap;
+    }
+
+    @Override
+    public int compare(AhatInstance a, AhatInstance b) {
+      return Long.compare(b.getRetainedSize(mHeap), a.getRetainedSize(mHeap));
+    }
+  }
+
+  /**
+   * Compare objects based on a list of comparators, giving priority to the
+   * earlier comparators in the list.
+   */
+  public static class WithPriority<T> implements Comparator<T> {
+    private List<Comparator<T>> mComparators;
+
+    public WithPriority(Comparator<T>... comparators) {
+      mComparators = Arrays.asList(comparators);
+    }
+
+    public WithPriority(List<Comparator<T>> comparators) {
+      mComparators = comparators;
+    }
+
+    @Override
+    public int compare(T a, T b) {
+      int res = 0;
+      Iterator<Comparator<T>> iter = mComparators.iterator();
+      while (res == 0 && iter.hasNext()) {
+        res = iter.next().compare(a, b);
+      }
+      return res;
+    }
+  }
+
+  public static Comparator<AhatInstance> defaultInstanceCompare(AhatSnapshot snapshot) {
+    List<Comparator<AhatInstance>> comparators = new ArrayList<Comparator<AhatInstance>>();
+
+    // Priority goes to the app heap, if we can find one.
+    AhatHeap appHeap = snapshot.getHeap("app");
+    if (appHeap != null) {
+      comparators.add(new InstanceByHeapRetainedSize(appHeap));
+    }
+
+    // Next is by total retained size.
+    comparators.add(INSTANCE_BY_TOTAL_RETAINED_SIZE);
+    return new WithPriority<AhatInstance>(comparators);
+  }
+
+  /**
+   * Compare Sites by the size of objects allocated on a given heap.
+   * Different object infos with the same size on the given heap are
+   * considered equal for the purposes of comparison.
+   * This sorts sites from larger size to smaller size.
+   */
+  public static class SiteByHeapSize implements Comparator<Site> {
+    AhatHeap mHeap;
+
+    public SiteByHeapSize(AhatHeap heap) {
+      mHeap = heap;
+    }
+
+    @Override
+    public int compare(Site a, Site b) {
+      return Long.compare(b.getSize(mHeap), a.getSize(mHeap));
+    }
+  }
+
+  /**
+   * Compare Sites by the total size of objects allocated.
+   * This sorts sites from larger size to smaller size.
+   */
+  public static final Comparator<Site> SITE_BY_TOTAL_SIZE = new Comparator<Site>() {
+    @Override
+    public int compare(Site a, Site b) {
+      return Long.compare(b.getTotalSize(), a.getTotalSize());
+    }
+  };
+
+  public static Comparator<Site> defaultSiteCompare(AhatSnapshot snapshot) {
+    List<Comparator<Site>> comparators = new ArrayList<Comparator<Site>>();
+
+    // Priority goes to the app heap, if we can find one.
+    AhatHeap appHeap = snapshot.getHeap("app");
+    if (appHeap != null) {
+      comparators.add(new SiteByHeapSize(appHeap));
+    }
+
+    // Next is by total size.
+    comparators.add(SITE_BY_TOTAL_SIZE);
+    return new WithPriority<Site>(comparators);
+  }
+
+  /**
+   * Compare Site.ObjectsInfo by their size.
+   * Different object infos with the same total retained size are considered
+   * equal for the purposes of comparison.
+   * This sorts object infos from larger retained size to smaller size.
+   */
+  public static final Comparator<Site.ObjectsInfo> OBJECTS_INFO_BY_SIZE
+    = new Comparator<Site.ObjectsInfo>() {
+    @Override
+    public int compare(Site.ObjectsInfo a, Site.ObjectsInfo b) {
+      return Long.compare(b.numBytes, a.numBytes);
+    }
+  };
+
+  /**
+   * Compare Site.ObjectsInfo by heap name.
+   * Different object infos with the same heap name are considered equal for
+   * the purposes of comparison.
+   */
+  public static final Comparator<Site.ObjectsInfo> OBJECTS_INFO_BY_HEAP_NAME
+    = new Comparator<Site.ObjectsInfo>() {
+    @Override
+    public int compare(Site.ObjectsInfo a, Site.ObjectsInfo b) {
+      return a.heap.getName().compareTo(b.heap.getName());
+    }
+  };
+
+  /**
+   * Compare Site.ObjectsInfo by class name.
+   * Different object infos with the same class name are considered equal for
+   * the purposes of comparison.
+   */
+  public static final Comparator<Site.ObjectsInfo> OBJECTS_INFO_BY_CLASS_NAME
+    = new Comparator<Site.ObjectsInfo>() {
+    @Override
+    public int compare(Site.ObjectsInfo a, Site.ObjectsInfo b) {
+      String aName = a.getClassName();
+      String bName = b.getClassName();
+      return aName.compareTo(bName);
+    }
+  };
+}
+
diff --git a/tools/ahat/src/heapdump/Value.java b/tools/ahat/src/heapdump/Value.java
new file mode 100644
index 0000000..6b2d38f
--- /dev/null
+++ b/tools/ahat/src/heapdump/Value.java
@@ -0,0 +1,133 @@
+/*
+ * 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.
+ */
+
+package com.android.ahat.heapdump;
+
+/**
+ * Value represents a field value in a heap dump. The field value is either a
+ * subclass of AhatInstance or a primitive Java type.
+ */
+public class Value {
+  private Object mObject;
+
+  /**
+   * Constructs a value from a generic Java Object.
+   * The Object must either be a boxed Java primitive type or a subclass of
+   * AhatInstance. The object must not be null.
+   */
+  Value(Object object) {
+    // TODO: Check that the Object is either an AhatSnapshot or boxed Java
+    // primitive type?
+    assert object != null;
+    mObject = object;
+  }
+
+  /**
+   * Returns true if the Value is an AhatInstance, as opposed to a Java
+   * primitive value.
+   */
+  public boolean isAhatInstance() {
+    return mObject instanceof AhatInstance;
+  }
+
+  /**
+   * Return the Value as an AhatInstance if it is one.
+   * Returns null if the Value represents a Java primitive value.
+   */
+  public AhatInstance asAhatInstance() {
+    if (isAhatInstance()) {
+      return (AhatInstance)mObject;
+    }
+    return null;
+  }
+
+  /**
+   * Returns true if the Value is an Integer.
+   */
+  public boolean isInteger() {
+    return mObject instanceof Integer;
+  }
+
+  /**
+   * Return the Value as an Integer if it is one.
+   * Returns null if the Value does not represent an Integer.
+   */
+  public Integer asInteger() {
+    if (isInteger()) {
+      return (Integer)mObject;
+    }
+    return null;
+  }
+
+  /**
+   * Returns true if the Value is an Long.
+   */
+  public boolean isLong() {
+    return mObject instanceof Long;
+  }
+
+  /**
+   * Return the Value as an Long if it is one.
+   * Returns null if the Value does not represent an Long.
+   */
+  public Long asLong() {
+    if (isLong()) {
+      return (Long)mObject;
+    }
+    return null;
+  }
+
+  /**
+   * Return the Value as a Byte if it is one.
+   * Returns null if the Value does not represent a Byte.
+   */
+  public Byte asByte() {
+    if (mObject instanceof Byte) {
+      return (Byte)mObject;
+    }
+    return null;
+  }
+
+  /**
+   * Return the Value as a Char if it is one.
+   * Returns null if the Value does not represent a Char.
+   */
+  public Character asChar() {
+    if (mObject instanceof Character) {
+      return (Character)mObject;
+    }
+    return null;
+  }
+
+  public String toString() {
+    return mObject.toString();
+  }
+
+  public static Value getBaseline(Value value) {
+    if (value == null || !value.isAhatInstance()) {
+      return value;
+    }
+    return new Value(value.asAhatInstance().getBaseline());
+  }
+
+  @Override public boolean equals(Object other) {
+    if (other instanceof Value) {
+      Value value = (Value)other;
+      return mObject.equals(value.mObject);
+    }
+    return false;
+  }
+}
diff --git a/tools/ahat/src/help.html b/tools/ahat/src/help.html
deleted file mode 100644
index ff04ad2..0000000
--- a/tools/ahat/src/help.html
+++ /dev/null
@@ -1,80 +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.
--->
-
-<h1>Help</h1>
-<h2>Information shown by ahat:</h2>
-<ul>
-  <li><a href="/">The total bytes retained by heap.</a></li>
-  <li><a href="/rooted">A list of rooted objects and their retained sizes for each heap.</a></li>
-  <li>Information about each allocated object:
-    <ul>
-      <li>The allocation site (stack trace) of the object (if available).</li>
-      <li>The dominator path from a root to the object.</li>
-      <li>The class, (shallow) size, retained size, and heap of the object.</li>
-      <li>The bitmap image for the object if the object represents a bitmap.</li>
-      <li>The instance fields or array elements of the object.</li>
-      <li>The super class, class loader, and static fields of class objects.</li>
-      <li>Other objects with references to the object.</li>
-      <li>Other objects immediately dominated by the object.</li>
-    </ul>
-  </li>
-  <li>A list of objects, optionally filtered by class, allocation site, and/or
-    heap.</li>
-  <li><a href="site">Information about each allocation site:</a>
-    <ul>
-      <li>The stack trace for the allocation site.</li>
-      <li>The number of bytes allocated at the allocation site.</li>
-      <li>Child sites called from the allocation site.</li>
-      <li>The size and count of objects allocated at the site, organized by
-        heap and object type.</li>
-    </ul>
-  </li>
-</ul>
-
-<h2>Tips:</h2>
-<h3>Heaps</h3>
-<p>
-Android heap dumps contain information for multiple heaps. The <b>app</b> heap
-is the memory used by your application. The <b>zygote</b> and <b>image</b>
-heaps are used by the system. You should ignore everything in the zygote and
-image heap and look only at the app heap. This is because changes in your
-application will not effect the zygote or image heaps, and because the zygote
-and image heaps are shared, they don't contribute significantly to your
-applications PSS.
-</p>
-
-<h3>Bitmaps</h3>
-<p>
-Bitmaps store their data using byte[] arrays. Whenever you see a large
-byte[], check if it is a bitmap by looking to see if there is a single
-android.graphics.Bitmap object referring to it. The byte[] will be marked as a
-root, but it is really being retained by the android.graphics.Bitmap object.
-</p>
-
-<h3>DexCaches</h3>
-<p>
-For each DexFile you load, there will be a corresponding DexCache whose size
-is proportional to the number of strings, fields, methods, and classes in your
-dex file. The DexCache entries may or may not be visible depending on the
-version of the Android platform the heap dump is from.
-</p>
-
-<h3>FinalizerReferences</h3>
-<p>
-A FinalizerReference is allocated for every object on the heap that has a
-non-trivial finalizer. These are stored in a linked list reachable from the
-FinalizerReference class object.
-</p>
diff --git a/tools/ahat/src/manifest.txt b/tools/ahat/src/manifest.txt
index 1993910..20245f3 100644
--- a/tools/ahat/src/manifest.txt
+++ b/tools/ahat/src/manifest.txt
@@ -1,4 +1,4 @@
 Name: ahat/
 Implementation-Title: ahat
-Implementation-Version: 0.8
+Implementation-Version: 1.1
 Main-Class: com.android.ahat.Main
diff --git a/tools/ahat/src/style.css b/tools/ahat/src/style.css
index ca074a5..47fae1d 100644
--- a/tools/ahat/src/style.css
+++ b/tools/ahat/src/style.css
@@ -18,6 +18,14 @@
   background-color: #eeffff;
 }
 
+span.added {
+  color: #770000;
+}
+
+span.removed {
+  color: #007700;
+}
+
 /*
  * Most of the columns show numbers of bytes. Numbers should be right aligned.
  */
diff --git a/tools/ahat/test-dump/Main.java b/tools/ahat/test-dump/Main.java
index 587d9de..7a05b1c 100644
--- a/tools/ahat/test-dump/Main.java
+++ b/tools/ahat/test-dump/Main.java
@@ -18,8 +18,9 @@
 import java.io.IOException;
 import java.lang.ref.PhantomReference;
 import java.lang.ref.ReferenceQueue;
+import java.lang.ref.SoftReference;
 import java.lang.ref.WeakReference;
-import libcore.util.NativeAllocationRegistry;
+import org.apache.harmony.dalvik.ddmc.DdmVmInternal;
 
 /**
  * Program used to create a heap dump for test purposes.
@@ -39,13 +40,32 @@
     }
   }
 
+  public static class AddedObject {
+  }
+
+  public static class RemovedObject {
+  }
+
+  public static class UnchangedObject {
+  }
+
+  public static class ModifiedObject {
+    public int value;
+    public String modifiedRefField;
+    public String unmodifiedRefField;
+  }
+
+  public static class StackSmasher {
+    public StackSmasher child;
+  }
+
   // We will take a heap dump that includes a single instance of this
   // DumpedStuff class. Objects stored as fields in this class can be easily
   // found in the hprof dump by searching for the instance of the DumpedStuff
   // class and reading the desired field.
   public static class DumpedStuff {
     public String basicString = "hello, world";
-    public String nonAscii = "Sigma (\u01a9) is not ASCII";
+    public String nonAscii = "Sigma (Æ©) is not ASCII";
     public String embeddedZero = "embedded\0...";  // Non-ASCII for string compression purposes.
     public char[] charArray = "char thing".toCharArray();
     public String nullString = null;
@@ -53,23 +73,53 @@
     public ReferenceQueue<Object> referenceQueue = new ReferenceQueue<Object>();
     public PhantomReference aPhantomReference = new PhantomReference(anObject, referenceQueue);
     public WeakReference aWeakReference = new WeakReference(anObject, referenceQueue);
+    public WeakReference aNullReferentReference = new WeakReference(null, referenceQueue);
+    public SoftReference aSoftReference = new SoftReference(new Object());
     public byte[] bigArray;
     public ObjectTree[] gcPathArray = new ObjectTree[]{null, null,
       new ObjectTree(
           new ObjectTree(null, new ObjectTree(null, null)),
           new ObjectTree(null, null)),
       null};
+    public Object[] basicStringRef;
+    public AddedObject addedObject;
+    public UnchangedObject unchangedObject = new UnchangedObject();
+    public RemovedObject removedObject;
+    public ModifiedObject modifiedObject;
+    public StackSmasher stackSmasher;
+    public StackSmasher stackSmasherAdded;
+    public static String modifiedStaticField;
+    public int[] modifiedArray;
 
-    DumpedStuff() {
-      int N = 1000000;
+    DumpedStuff(boolean baseline) {
+      int N = baseline ? 400000 : 1000000;
       bigArray = new byte[N];
       for (int i = 0; i < N; i++) {
         bigArray[i] = (byte)((i*i) & 0xFF);
       }
 
-      NativeAllocationRegistry registry = new NativeAllocationRegistry(
-          Main.class.getClassLoader(), 0x12345, 42);
-      registry.registerNativeAllocation(anObject, 0xABCDABCD);
+      addedObject = baseline ? null : new AddedObject();
+      removedObject = baseline ? new RemovedObject() : null;
+      modifiedObject = new ModifiedObject();
+      modifiedObject.value = baseline ? 5 : 8;
+      modifiedObject.modifiedRefField = baseline ? "A1" : "A2";
+      modifiedObject.unmodifiedRefField = "B";
+      modifiedStaticField = baseline ? "C1" : "C2";
+      modifiedArray = baseline ? new int[]{0,1,2,3} : new int[]{3,1,2,0};
+
+      // Deep matching dominator trees shouldn't smash the stack when we try
+      // to diff them. Make some deep dominator trees to help test it.
+      for (int i = 0; i < 10000; i++) {
+        StackSmasher smasher = new StackSmasher();
+        smasher.child = stackSmasher;
+        stackSmasher = smasher;
+
+        if (!baseline) {
+          smasher = new StackSmasher();
+          smasher.child = stackSmasherAdded;
+          stackSmasherAdded = smasher;
+        }
+      }
 
       gcPathArray[2].right.left = gcPathArray[2].left.right;
     }
@@ -82,8 +132,21 @@
     }
     String file = args[0];
 
+    // If a --base argument is provided, it means we should generate a
+    // baseline hprof file suitable for using in testing diff.
+    boolean baseline = args.length > 1 && args[1].equals("--base");
+
+    // Enable allocation tracking so we get stack traces in the heap dump.
+    DdmVmInternal.enableRecentAllocations(true);
+
     // Allocate the instance of DumpedStuff.
-    stuff = new DumpedStuff();
+    stuff = new DumpedStuff(baseline);
+
+    // Create a bunch of unreachable objects pointing to basicString for the
+    // reverseReferencesAreNotUnreachable test
+    for (int i = 0; i < 100; i++) {
+      stuff.basicStringRef = new Object[]{stuff.basicString};
+    }
 
     // Take a heap dump that will include that instance of DumpedStuff.
     System.err.println("Dumping hprof data to " + file);
diff --git a/tools/ahat/test/DiffTest.java b/tools/ahat/test/DiffTest.java
new file mode 100644
index 0000000..52b6b7b
--- /dev/null
+++ b/tools/ahat/test/DiffTest.java
@@ -0,0 +1,163 @@
+/*
+ * 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.
+ */
+
+package com.android.ahat;
+
+import com.android.ahat.heapdump.AhatHeap;
+import com.android.ahat.heapdump.AhatInstance;
+import com.android.ahat.heapdump.AhatSnapshot;
+import com.android.ahat.heapdump.Diff;
+import com.android.ahat.heapdump.FieldValue;
+import com.android.tools.perflib.heap.hprof.HprofClassDump;
+import com.android.tools.perflib.heap.hprof.HprofConstant;
+import com.android.tools.perflib.heap.hprof.HprofDumpRecord;
+import com.android.tools.perflib.heap.hprof.HprofHeapDump;
+import com.android.tools.perflib.heap.hprof.HprofInstanceDump;
+import com.android.tools.perflib.heap.hprof.HprofInstanceField;
+import com.android.tools.perflib.heap.hprof.HprofLoadClass;
+import com.android.tools.perflib.heap.hprof.HprofPrimitiveArrayDump;
+import com.android.tools.perflib.heap.hprof.HprofRecord;
+import com.android.tools.perflib.heap.hprof.HprofRootDebugger;
+import com.android.tools.perflib.heap.hprof.HprofStaticField;
+import com.android.tools.perflib.heap.hprof.HprofStringBuilder;
+import com.android.tools.perflib.heap.hprof.HprofType;
+import com.google.common.io.ByteArrayDataOutput;
+import com.google.common.io.ByteStreams;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+public class DiffTest {
+  @Test
+  public void diffMatchedHeap() throws IOException {
+    TestDump dump = TestDump.getTestDump();
+    AhatHeap a = dump.getAhatSnapshot().getHeap("app");
+    assertNotNull(a);
+    AhatHeap b = dump.getBaselineAhatSnapshot().getHeap("app");
+    assertNotNull(b);
+    assertEquals(a.getBaseline(), b);
+    assertEquals(b.getBaseline(), a);
+  }
+
+  @Test
+  public void diffUnchanged() throws IOException {
+    TestDump dump = TestDump.getTestDump();
+
+    AhatInstance a = dump.getDumpedAhatInstance("unchangedObject");
+    assertNotNull(a);
+
+    AhatInstance b = dump.getBaselineDumpedAhatInstance("unchangedObject");
+    assertNotNull(b);
+    assertEquals(a, b.getBaseline());
+    assertEquals(b, a.getBaseline());
+    assertEquals(a.getSite(), b.getSite().getBaseline());
+    assertEquals(b.getSite(), a.getSite().getBaseline());
+  }
+
+  @Test
+  public void diffAdded() throws IOException {
+    TestDump dump = TestDump.getTestDump();
+
+    AhatInstance a = dump.getDumpedAhatInstance("addedObject");
+    assertNotNull(a);
+    assertNull(dump.getBaselineDumpedAhatInstance("addedObject"));
+    assertTrue(a.getBaseline().isPlaceHolder());
+  }
+
+  @Test
+  public void diffRemoved() throws IOException {
+    TestDump dump = TestDump.getTestDump();
+
+    assertNull(dump.getDumpedAhatInstance("removedObject"));
+    AhatInstance b = dump.getBaselineDumpedAhatInstance("removedObject");
+    assertNotNull(b);
+    assertTrue(b.getBaseline().isPlaceHolder());
+  }
+
+  @Test
+  public void nullClassObj() throws IOException {
+    // Set up a heap dump that has a null classObj.
+    // The heap dump is derived from the InstanceTest.asStringEmbedded test.
+    HprofStringBuilder strings = new HprofStringBuilder(0);
+    List<HprofRecord> records = new ArrayList<HprofRecord>();
+    List<HprofDumpRecord> dump = new ArrayList<HprofDumpRecord>();
+
+    final int stringClassObjectId = 1;
+    records.add(new HprofLoadClass(0, 0, stringClassObjectId, 0, strings.get("java.lang.String")));
+    dump.add(new HprofClassDump(stringClassObjectId, 0, 0, 0, 0, 0, 0, 0, 0,
+          new HprofConstant[0], new HprofStaticField[0],
+          new HprofInstanceField[]{
+            new HprofInstanceField(strings.get("count"), HprofType.TYPE_INT),
+            new HprofInstanceField(strings.get("hashCode"), HprofType.TYPE_INT),
+            new HprofInstanceField(strings.get("offset"), HprofType.TYPE_INT),
+            new HprofInstanceField(strings.get("value"), HprofType.TYPE_OBJECT)}));
+
+    dump.add(new HprofPrimitiveArrayDump(0x41, 0, HprofType.TYPE_CHAR,
+          new long[]{'n', 'o', 't', ' ', 'h', 'e', 'l', 'l', 'o', 'o', 'p'}));
+
+    ByteArrayDataOutput values = ByteStreams.newDataOutput();
+    values.writeInt(5);     // count
+    values.writeInt(0);     // hashCode
+    values.writeInt(4);     // offset
+    values.writeInt(0x41);  // value
+    dump.add(new HprofInstanceDump(0x42, 0, stringClassObjectId, values.toByteArray()));
+    dump.add(new HprofRootDebugger(stringClassObjectId));
+    dump.add(new HprofRootDebugger(0x42));
+
+    records.add(new HprofHeapDump(0, dump.toArray(new HprofDumpRecord[0])));
+    AhatSnapshot snapshot = SnapshotBuilder.makeSnapshot(strings, records);
+
+    // Diffing should not crash.
+    Diff.snapshots(snapshot, snapshot);
+  }
+
+  @Test
+  public void diffFields() {
+    List<FieldValue> a = new ArrayList<FieldValue>();
+    a.add(new FieldValue("n0", "t0", null));
+    a.add(new FieldValue("n2", "t2", null));
+    a.add(new FieldValue("n3", "t3", null));
+    a.add(new FieldValue("n4", "t4", null));
+    a.add(new FieldValue("n5", "t5", null));
+    a.add(new FieldValue("n6", "t6", null));
+
+    List<FieldValue> b = new ArrayList<FieldValue>();
+    b.add(new FieldValue("n0", "t0", null));
+    b.add(new FieldValue("n1", "t1", null));
+    b.add(new FieldValue("n2", "t2", null));
+    b.add(new FieldValue("n3", "t3", null));
+    b.add(new FieldValue("n5", "t5", null));
+    b.add(new FieldValue("n6", "t6", null));
+    b.add(new FieldValue("n7", "t7", null));
+
+    Diff.fields(a, b);
+    assertEquals(8, a.size());
+    assertEquals(8, b.size());
+    for (int i = 0; i < 8; i++) {
+      assertEquals(a.get(i), b.get(i).getBaseline());
+      assertEquals(b.get(i), a.get(i).getBaseline());
+    }
+    assertTrue(a.get(1).isPlaceHolder());
+    assertTrue(a.get(7).isPlaceHolder());
+    assertTrue(b.get(4).isPlaceHolder());
+  }
+}
diff --git a/tools/ahat/test/InstanceTest.java b/tools/ahat/test/InstanceTest.java
new file mode 100644
index 0000000..3a50150
--- /dev/null
+++ b/tools/ahat/test/InstanceTest.java
@@ -0,0 +1,413 @@
+/*
+ * 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.
+ */
+
+package com.android.ahat;
+
+import com.android.ahat.heapdump.AhatClassObj;
+import com.android.ahat.heapdump.AhatHeap;
+import com.android.ahat.heapdump.AhatInstance;
+import com.android.ahat.heapdump.AhatSnapshot;
+import com.android.ahat.heapdump.PathElement;
+import com.android.ahat.heapdump.Value;
+import com.android.tools.perflib.heap.hprof.HprofClassDump;
+import com.android.tools.perflib.heap.hprof.HprofConstant;
+import com.android.tools.perflib.heap.hprof.HprofDumpRecord;
+import com.android.tools.perflib.heap.hprof.HprofHeapDump;
+import com.android.tools.perflib.heap.hprof.HprofInstanceDump;
+import com.android.tools.perflib.heap.hprof.HprofInstanceField;
+import com.android.tools.perflib.heap.hprof.HprofLoadClass;
+import com.android.tools.perflib.heap.hprof.HprofPrimitiveArrayDump;
+import com.android.tools.perflib.heap.hprof.HprofRecord;
+import com.android.tools.perflib.heap.hprof.HprofRootDebugger;
+import com.android.tools.perflib.heap.hprof.HprofStaticField;
+import com.android.tools.perflib.heap.hprof.HprofStringBuilder;
+import com.android.tools.perflib.heap.hprof.HprofType;
+import com.google.common.io.ByteArrayDataOutput;
+import com.google.common.io.ByteStreams;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+public class InstanceTest {
+  @Test
+  public void asStringBasic() throws IOException {
+    TestDump dump = TestDump.getTestDump();
+    AhatInstance str = dump.getDumpedAhatInstance("basicString");
+    assertEquals("hello, world", str.asString());
+  }
+
+  @Test
+  public void asStringNonAscii() throws IOException {
+    TestDump dump = TestDump.getTestDump();
+    AhatInstance str = dump.getDumpedAhatInstance("nonAscii");
+    assertEquals("Sigma (Æ©) is not ASCII", str.asString());
+  }
+
+  @Test
+  public void asStringEmbeddedZero() throws IOException {
+    TestDump dump = TestDump.getTestDump();
+    AhatInstance str = dump.getDumpedAhatInstance("embeddedZero");
+    assertEquals("embedded\0...", str.asString());
+  }
+
+  @Test
+  public void asStringCharArray() throws IOException {
+    TestDump dump = TestDump.getTestDump();
+    AhatInstance str = dump.getDumpedAhatInstance("charArray");
+    assertEquals("char thing", str.asString());
+  }
+
+  @Test
+  public void asStringTruncated() throws IOException {
+    TestDump dump = TestDump.getTestDump();
+    AhatInstance str = dump.getDumpedAhatInstance("basicString");
+    assertEquals("hello", str.asString(5));
+  }
+
+  @Test
+  public void asStringTruncatedNonAscii() throws IOException {
+    TestDump dump = TestDump.getTestDump();
+    AhatInstance str = dump.getDumpedAhatInstance("nonAscii");
+    assertEquals("Sigma (Æ©)", str.asString(9));
+  }
+
+  @Test
+  public void asStringTruncatedEmbeddedZero() throws IOException {
+    TestDump dump = TestDump.getTestDump();
+    AhatInstance str = dump.getDumpedAhatInstance("embeddedZero");
+    assertEquals("embed", str.asString(5));
+  }
+
+  @Test
+  public void asStringCharArrayTruncated() throws IOException {
+    TestDump dump = TestDump.getTestDump();
+    AhatInstance str = dump.getDumpedAhatInstance("charArray");
+    assertEquals("char ", str.asString(5));
+  }
+
+  @Test
+  public void asStringExactMax() throws IOException {
+    TestDump dump = TestDump.getTestDump();
+    AhatInstance str = dump.getDumpedAhatInstance("basicString");
+    assertEquals("hello, world", str.asString(12));
+  }
+
+  @Test
+  public void asStringExactMaxNonAscii() throws IOException {
+    TestDump dump = TestDump.getTestDump();
+    AhatInstance str = dump.getDumpedAhatInstance("nonAscii");
+    assertEquals("Sigma (Æ©) is not ASCII", str.asString(22));
+  }
+
+  @Test
+  public void asStringExactMaxEmbeddedZero() throws IOException {
+    TestDump dump = TestDump.getTestDump();
+    AhatInstance str = dump.getDumpedAhatInstance("embeddedZero");
+    assertEquals("embedded\0...", str.asString(12));
+  }
+
+  @Test
+  public void asStringCharArrayExactMax() throws IOException {
+    TestDump dump = TestDump.getTestDump();
+    AhatInstance str = dump.getDumpedAhatInstance("charArray");
+    assertEquals("char thing", str.asString(10));
+  }
+
+  @Test
+  public void asStringNotTruncated() throws IOException {
+    TestDump dump = TestDump.getTestDump();
+    AhatInstance str = dump.getDumpedAhatInstance("basicString");
+    assertEquals("hello, world", str.asString(50));
+  }
+
+  @Test
+  public void asStringNotTruncatedNonAscii() throws IOException {
+    TestDump dump = TestDump.getTestDump();
+    AhatInstance str = dump.getDumpedAhatInstance("nonAscii");
+    assertEquals("Sigma (Æ©) is not ASCII", str.asString(50));
+  }
+
+  @Test
+  public void asStringNotTruncatedEmbeddedZero() throws IOException {
+    TestDump dump = TestDump.getTestDump();
+    AhatInstance str = dump.getDumpedAhatInstance("embeddedZero");
+    assertEquals("embedded\0...", str.asString(50));
+  }
+
+  @Test
+  public void asStringCharArrayNotTruncated() throws IOException {
+    TestDump dump = TestDump.getTestDump();
+    AhatInstance str = dump.getDumpedAhatInstance("charArray");
+    assertEquals("char thing", str.asString(50));
+  }
+
+  @Test
+  public void asStringNegativeMax() throws IOException {
+    TestDump dump = TestDump.getTestDump();
+    AhatInstance str = dump.getDumpedAhatInstance("basicString");
+    assertEquals("hello, world", str.asString(-3));
+  }
+
+  @Test
+  public void asStringNegativeMaxNonAscii() throws IOException {
+    TestDump dump = TestDump.getTestDump();
+    AhatInstance str = dump.getDumpedAhatInstance("nonAscii");
+    assertEquals("Sigma (Æ©) is not ASCII", str.asString(-3));
+  }
+
+  @Test
+  public void asStringNegativeMaxEmbeddedZero() throws IOException {
+    TestDump dump = TestDump.getTestDump();
+    AhatInstance str = dump.getDumpedAhatInstance("embeddedZero");
+    assertEquals("embedded\0...", str.asString(-3));
+  }
+
+  @Test
+  public void asStringCharArrayNegativeMax() throws IOException {
+    TestDump dump = TestDump.getTestDump();
+    AhatInstance str = dump.getDumpedAhatInstance("charArray");
+    assertEquals("char thing", str.asString(-3));
+  }
+
+  @Test
+  public void asStringNull() throws IOException {
+    TestDump dump = TestDump.getTestDump();
+    AhatInstance obj = dump.getDumpedAhatInstance("nullString");
+    assertNull(obj);
+  }
+
+  @Test
+  public void asStringNotString() throws IOException {
+    TestDump dump = TestDump.getTestDump();
+    AhatInstance obj = dump.getDumpedAhatInstance("anObject");
+    assertNotNull(obj);
+    assertNull(obj.asString());
+  }
+
+  @Test
+  public void basicReference() throws IOException {
+    TestDump dump = TestDump.getTestDump();
+
+    AhatInstance pref = dump.getDumpedAhatInstance("aPhantomReference");
+    AhatInstance wref = dump.getDumpedAhatInstance("aWeakReference");
+    AhatInstance nref = dump.getDumpedAhatInstance("aNullReferentReference");
+    AhatInstance referent = dump.getDumpedAhatInstance("anObject");
+    assertNotNull(pref);
+    assertNotNull(wref);
+    assertNotNull(nref);
+    assertNotNull(referent);
+    assertEquals(referent, pref.getReferent());
+    assertEquals(referent, wref.getReferent());
+    assertNull(nref.getReferent());
+    assertNull(referent.getReferent());
+  }
+
+  @Test
+  public void unreachableReferent() throws IOException {
+    // The test dump program should never be under enough GC pressure for the
+    // soft reference to be cleared. Ensure that ahat will show the soft
+    // reference as having a non-null referent.
+    TestDump dump = TestDump.getTestDump();
+    AhatInstance ref = dump.getDumpedAhatInstance("aSoftReference");
+    assertNotNull(ref.getReferent());
+  }
+
+  @Test
+  public void gcRootPath() throws IOException {
+    TestDump dump = TestDump.getTestDump();
+
+    AhatClassObj main = dump.getAhatSnapshot().findClass("Main");
+    AhatInstance gcPathArray = dump.getDumpedAhatInstance("gcPathArray");
+    Value value = gcPathArray.asArrayInstance().getValue(2);
+    AhatInstance base = value.asAhatInstance();
+    AhatInstance left = base.getRefField("left");
+    AhatInstance right = base.getRefField("right");
+    AhatInstance target = left.getRefField("right");
+
+    List<PathElement> path = target.getPathFromGcRoot();
+    assertEquals(6, path.size());
+
+    assertEquals(main, path.get(0).instance);
+    assertEquals(".stuff", path.get(0).field);
+    assertTrue(path.get(0).isDominator);
+
+    assertEquals(".gcPathArray", path.get(1).field);
+    assertTrue(path.get(1).isDominator);
+
+    assertEquals(gcPathArray, path.get(2).instance);
+    assertEquals("[2]", path.get(2).field);
+    assertTrue(path.get(2).isDominator);
+
+    assertEquals(base, path.get(3).instance);
+    assertTrue(path.get(3).isDominator);
+
+    // There are two possible paths. Either it can go through the 'left' node,
+    // or the 'right' node.
+    if (path.get(3).field.equals(".left")) {
+      assertEquals(".left", path.get(3).field);
+
+      assertEquals(left, path.get(4).instance);
+      assertEquals(".right", path.get(4).field);
+      assertFalse(path.get(4).isDominator);
+
+    } else {
+      assertEquals(".right", path.get(3).field);
+
+      assertEquals(right, path.get(4).instance);
+      assertEquals(".left", path.get(4).field);
+      assertFalse(path.get(4).isDominator);
+    }
+
+    assertEquals(target, path.get(5).instance);
+    assertEquals("", path.get(5).field);
+    assertTrue(path.get(5).isDominator);
+  }
+
+  @Test
+  public void retainedSize() throws IOException {
+    TestDump dump = TestDump.getTestDump();
+
+    // anObject should not be an immediate dominator of any other object. This
+    // means its retained size should be equal to its size for the heap it was
+    // allocated on, and should be 0 for all other heaps.
+    AhatInstance anObject = dump.getDumpedAhatInstance("anObject");
+    AhatSnapshot snapshot = dump.getAhatSnapshot();
+    long size = anObject.getSize();
+    assertEquals(size, anObject.getTotalRetainedSize());
+    assertEquals(size, anObject.getRetainedSize(anObject.getHeap()));
+    for (AhatHeap heap : snapshot.getHeaps()) {
+      if (!heap.equals(anObject.getHeap())) {
+        assertEquals(String.format("For heap '%s'", heap.getName()),
+            0, anObject.getRetainedSize(heap));
+      }
+    }
+  }
+
+  @Test
+  public void objectNotABitmap() throws IOException {
+    TestDump dump = TestDump.getTestDump();
+    AhatInstance obj = dump.getDumpedAhatInstance("anObject");
+    assertNull(obj.asBitmap());
+  }
+
+  @Test
+  public void arrayNotABitmap() throws IOException {
+    TestDump dump = TestDump.getTestDump();
+    AhatInstance obj = dump.getDumpedAhatInstance("gcPathArray");
+    assertNull(obj.asBitmap());
+  }
+
+  @Test
+  public void classObjNotABitmap() throws IOException {
+    TestDump dump = TestDump.getTestDump();
+    AhatInstance obj = dump.getAhatSnapshot().findClass("Main");
+    assertNull(obj.asBitmap());
+  }
+
+  @Test
+  public void classInstanceToString() throws IOException {
+    TestDump dump = TestDump.getTestDump();
+    AhatInstance obj = dump.getDumpedAhatInstance("aPhantomReference");
+    long id = obj.getId();
+    assertEquals(String.format("java.lang.ref.PhantomReference@%08x", id), obj.toString());
+  }
+
+  @Test
+  public void classObjToString() throws IOException {
+    TestDump dump = TestDump.getTestDump();
+    AhatInstance obj = dump.getAhatSnapshot().findClass("Main");
+    assertEquals("Main", obj.toString());
+  }
+
+  @Test
+  public void arrayInstanceToString() throws IOException {
+    TestDump dump = TestDump.getTestDump();
+    AhatInstance obj = dump.getDumpedAhatInstance("gcPathArray");
+    long id = obj.getId();
+
+    // There's a bug in perfib's proguard deobfuscation for arrays.
+    // To work around that bug for the time being, only test the suffix of
+    // the toString result. Ideally we test for string equality against
+    // "Main$ObjectTree[4]@%08x", id.
+    assertTrue(obj.toString().endsWith(String.format("[4]@%08x", id)));
+  }
+
+  @Test
+  public void primArrayInstanceToString() throws IOException {
+    TestDump dump = TestDump.getTestDump();
+    AhatInstance obj = dump.getDumpedAhatInstance("bigArray");
+    long id = obj.getId();
+    assertEquals(String.format("byte[1000000]@%08x", id), obj.toString());
+  }
+
+  @Test
+  public void isNotRoot() throws IOException {
+    TestDump dump = TestDump.getTestDump();
+    AhatInstance obj = dump.getDumpedAhatInstance("anObject");
+    assertFalse(obj.isRoot());
+    assertNull(obj.getRootTypes());
+  }
+
+  @Test
+  public void asStringEmbedded() throws IOException {
+    // Set up a heap dump with an instance of java.lang.String of
+    // "hello" with instance id 0x42 that is backed by a char array that is
+    // bigger. This is how ART used to represent strings, and we should still
+    // support it in case the heap dump is from a previous platform version.
+    HprofStringBuilder strings = new HprofStringBuilder(0);
+    List<HprofRecord> records = new ArrayList<HprofRecord>();
+    List<HprofDumpRecord> dump = new ArrayList<HprofDumpRecord>();
+
+    final int stringClassObjectId = 1;
+    records.add(new HprofLoadClass(0, 0, stringClassObjectId, 0, strings.get("java.lang.String")));
+    dump.add(new HprofClassDump(stringClassObjectId, 0, 0, 0, 0, 0, 0, 0, 0,
+          new HprofConstant[0], new HprofStaticField[0],
+          new HprofInstanceField[]{
+            new HprofInstanceField(strings.get("count"), HprofType.TYPE_INT),
+            new HprofInstanceField(strings.get("hashCode"), HprofType.TYPE_INT),
+            new HprofInstanceField(strings.get("offset"), HprofType.TYPE_INT),
+            new HprofInstanceField(strings.get("value"), HprofType.TYPE_OBJECT)}));
+
+    dump.add(new HprofPrimitiveArrayDump(0x41, 0, HprofType.TYPE_CHAR,
+          new long[]{'n', 'o', 't', ' ', 'h', 'e', 'l', 'l', 'o', 'o', 'p'}));
+
+    ByteArrayDataOutput values = ByteStreams.newDataOutput();
+    values.writeInt(5);     // count
+    values.writeInt(0);     // hashCode
+    values.writeInt(4);     // offset
+    values.writeInt(0x41);  // value
+    dump.add(new HprofInstanceDump(0x42, 0, stringClassObjectId, values.toByteArray()));
+    dump.add(new HprofRootDebugger(stringClassObjectId));
+    dump.add(new HprofRootDebugger(0x42));
+
+    records.add(new HprofHeapDump(0, dump.toArray(new HprofDumpRecord[0])));
+    AhatSnapshot snapshot = SnapshotBuilder.makeSnapshot(strings, records);
+    AhatInstance chars = snapshot.findInstance(0x41);
+    assertNotNull(chars);
+    assertEquals("not helloop", chars.asString());
+
+    AhatInstance stringInstance = snapshot.findInstance(0x42);
+    assertNotNull(stringInstance);
+    assertEquals("hello", stringInstance.asString());
+  }
+}
diff --git a/tools/ahat/test/InstanceUtilsTest.java b/tools/ahat/test/InstanceUtilsTest.java
deleted file mode 100644
index fe2706d..0000000
--- a/tools/ahat/test/InstanceUtilsTest.java
+++ /dev/null
@@ -1,252 +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.
- */
-
-package com.android.ahat;
-
-import com.android.tools.perflib.heap.ArrayInstance;
-import com.android.tools.perflib.heap.ClassObj;
-import com.android.tools.perflib.heap.Instance;
-import java.io.IOException;
-import java.util.List;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import org.junit.Test;
-
-public class InstanceUtilsTest {
-  @Test
-  public void asStringBasic() throws IOException {
-    TestDump dump = TestDump.getTestDump();
-    Instance str = (Instance)dump.getDumpedThing("basicString");
-    assertEquals("hello, world", InstanceUtils.asString(str));
-  }
-
-  @Test
-  public void asStringNonAscii() throws IOException {
-    TestDump dump = TestDump.getTestDump();
-    Instance str = (Instance)dump.getDumpedThing("nonAscii");
-    assertEquals("Sigma (\u01a9) is not ASCII", InstanceUtils.asString(str));
-  }
-
-  @Test
-  public void asStringEmbeddedZero() throws IOException {
-    TestDump dump = TestDump.getTestDump();
-    Instance str = (Instance)dump.getDumpedThing("embeddedZero");
-    assertEquals("embedded\0...", InstanceUtils.asString(str));
-  }
-
-  @Test
-  public void asStringCharArray() throws IOException {
-    TestDump dump = TestDump.getTestDump();
-    Instance str = (Instance)dump.getDumpedThing("charArray");
-    assertEquals("char thing", InstanceUtils.asString(str));
-  }
-
-  @Test
-  public void asStringTruncated() throws IOException {
-    TestDump dump = TestDump.getTestDump();
-    Instance str = (Instance)dump.getDumpedThing("basicString");
-    assertEquals("hello", InstanceUtils.asString(str, 5));
-  }
-
-  @Test
-  public void asStringTruncatedNonAscii() throws IOException {
-    TestDump dump = TestDump.getTestDump();
-    Instance str = (Instance)dump.getDumpedThing("nonAscii");
-    assertEquals("Sigma (\u01a9)", InstanceUtils.asString(str, 9));
-  }
-
-  @Test
-  public void asStringTruncatedEmbeddedZero() throws IOException {
-    TestDump dump = TestDump.getTestDump();
-    Instance str = (Instance)dump.getDumpedThing("embeddedZero");
-    assertEquals("embed", InstanceUtils.asString(str, 5));
-  }
-
-  @Test
-  public void asStringCharArrayTruncated() throws IOException {
-    TestDump dump = TestDump.getTestDump();
-    Instance str = (Instance)dump.getDumpedThing("charArray");
-    assertEquals("char ", InstanceUtils.asString(str, 5));
-  }
-
-  @Test
-  public void asStringExactMax() throws IOException {
-    TestDump dump = TestDump.getTestDump();
-    Instance str = (Instance)dump.getDumpedThing("basicString");
-    assertEquals("hello, world", InstanceUtils.asString(str, 12));
-  }
-
-  @Test
-  public void asStringExactMaxNonAscii() throws IOException {
-    TestDump dump = TestDump.getTestDump();
-    Instance str = (Instance)dump.getDumpedThing("nonAscii");
-    assertEquals("Sigma (\u01a9) is not ASCII", InstanceUtils.asString(str, 22));
-  }
-
-  @Test
-  public void asStringExactMaxEmbeddedZero() throws IOException {
-    TestDump dump = TestDump.getTestDump();
-    Instance str = (Instance)dump.getDumpedThing("embeddedZero");
-    assertEquals("embedded\0...", InstanceUtils.asString(str, 12));
-  }
-
-  @Test
-  public void asStringCharArrayExactMax() throws IOException {
-    TestDump dump = TestDump.getTestDump();
-    Instance str = (Instance)dump.getDumpedThing("charArray");
-    assertEquals("char thing", InstanceUtils.asString(str, 10));
-  }
-
-  @Test
-  public void asStringNotTruncated() throws IOException {
-    TestDump dump = TestDump.getTestDump();
-    Instance str = (Instance)dump.getDumpedThing("basicString");
-    assertEquals("hello, world", InstanceUtils.asString(str, 50));
-  }
-
-  @Test
-  public void asStringNotTruncatedNonAscii() throws IOException {
-    TestDump dump = TestDump.getTestDump();
-    Instance str = (Instance)dump.getDumpedThing("nonAscii");
-    assertEquals("Sigma (\u01a9) is not ASCII", InstanceUtils.asString(str, 50));
-  }
-
-  @Test
-  public void asStringNotTruncatedEmbeddedZero() throws IOException {
-    TestDump dump = TestDump.getTestDump();
-    Instance str = (Instance)dump.getDumpedThing("embeddedZero");
-    assertEquals("embedded\0...", InstanceUtils.asString(str, 50));
-  }
-
-  @Test
-  public void asStringCharArrayNotTruncated() throws IOException {
-    TestDump dump = TestDump.getTestDump();
-    Instance str = (Instance)dump.getDumpedThing("charArray");
-    assertEquals("char thing", InstanceUtils.asString(str, 50));
-  }
-
-  @Test
-  public void asStringNegativeMax() throws IOException {
-    TestDump dump = TestDump.getTestDump();
-    Instance str = (Instance)dump.getDumpedThing("basicString");
-    assertEquals("hello, world", InstanceUtils.asString(str, -3));
-  }
-
-  @Test
-  public void asStringNegativeMaxNonAscii() throws IOException {
-    TestDump dump = TestDump.getTestDump();
-    Instance str = (Instance)dump.getDumpedThing("nonAscii");
-    assertEquals("Sigma (\u01a9) is not ASCII", InstanceUtils.asString(str, -3));
-  }
-
-  @Test
-  public void asStringNegativeMaxEmbeddedZero() throws IOException {
-    TestDump dump = TestDump.getTestDump();
-    Instance str = (Instance)dump.getDumpedThing("embeddedZero");
-    assertEquals("embedded\0...", InstanceUtils.asString(str, -3));
-  }
-
-  @Test
-  public void asStringCharArrayNegativeMax() throws IOException {
-    TestDump dump = TestDump.getTestDump();
-    Instance str = (Instance)dump.getDumpedThing("charArray");
-    assertEquals("char thing", InstanceUtils.asString(str, -3));
-  }
-
-  @Test
-  public void asStringNull() throws IOException {
-    TestDump dump = TestDump.getTestDump();
-    Instance obj = (Instance)dump.getDumpedThing("nullString");
-    assertNull(InstanceUtils.asString(obj));
-  }
-
-  @Test
-  public void asStringNotString() throws IOException {
-    TestDump dump = TestDump.getTestDump();
-    Instance obj = (Instance)dump.getDumpedThing("anObject");
-    assertNotNull(obj);
-    assertNull(InstanceUtils.asString(obj));
-  }
-
-  @Test
-  public void basicReference() throws IOException {
-    TestDump dump = TestDump.getTestDump();
-
-    Instance pref = (Instance)dump.getDumpedThing("aPhantomReference");
-    Instance wref = (Instance)dump.getDumpedThing("aWeakReference");
-    Instance referent = (Instance)dump.getDumpedThing("anObject");
-    assertNotNull(pref);
-    assertNotNull(wref);
-    assertNotNull(referent);
-    assertEquals(referent, InstanceUtils.getReferent(pref));
-    assertEquals(referent, InstanceUtils.getReferent(wref));
-    assertNull(InstanceUtils.getReferent(referent));
-  }
-
-  @Test
-  public void gcRootPath() throws IOException {
-    TestDump dump = TestDump.getTestDump();
-
-    ClassObj main = dump.getAhatSnapshot().findClass("Main");
-    ArrayInstance gcPathArray = (ArrayInstance)dump.getDumpedThing("gcPathArray");
-    Object[] values = gcPathArray.getValues();
-    Instance base = (Instance)values[2];
-    Instance left = InstanceUtils.getRefField(base, "left");
-    Instance right = InstanceUtils.getRefField(base, "right");
-    Instance target = InstanceUtils.getRefField(left, "right");
-
-    List<InstanceUtils.PathElement> path = InstanceUtils.getPathFromGcRoot(target);
-    assertEquals(6, path.size());
-
-    assertEquals(main, path.get(0).instance);
-    assertEquals(".stuff", path.get(0).field);
-    assertTrue(path.get(0).isDominator);
-
-    assertEquals(".gcPathArray", path.get(1).field);
-    assertTrue(path.get(1).isDominator);
-
-    assertEquals(gcPathArray, path.get(2).instance);
-    assertEquals("[2]", path.get(2).field);
-    assertTrue(path.get(2).isDominator);
-
-    assertEquals(base, path.get(3).instance);
-    assertTrue(path.get(3).isDominator);
-
-    // There are two possible paths. Either it can go through the 'left' node,
-    // or the 'right' node.
-    if (path.get(3).field.equals(".left")) {
-      assertEquals(".left", path.get(3).field);
-
-      assertEquals(left, path.get(4).instance);
-      assertEquals(".right", path.get(4).field);
-      assertFalse(path.get(4).isDominator);
-
-    } else {
-      assertEquals(".right", path.get(3).field);
-
-      assertEquals(right, path.get(4).instance);
-      assertEquals(".left", path.get(4).field);
-      assertFalse(path.get(4).isDominator);
-    }
-
-    assertEquals(target, path.get(5).instance);
-    assertEquals("", path.get(5).field);
-    assertTrue(path.get(5).isDominator);
-  }
-}
diff --git a/tools/ahat/test/NativeAllocationTest.java b/tools/ahat/test/NativeAllocationTest.java
deleted file mode 100644
index 7ad4c1d..0000000
--- a/tools/ahat/test/NativeAllocationTest.java
+++ /dev/null
@@ -1,44 +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.
- */
-
-package com.android.ahat;
-
-import com.android.tools.perflib.heap.Instance;
-import java.io.IOException;
-import static org.junit.Assert.fail;
-import static org.junit.Assert.assertEquals;
-import org.junit.Test;
-
-public class NativeAllocationTest {
-
-  @Test
-  public void nativeAllocation() throws IOException {
-    TestDump dump = TestDump.getTestDump();
-
-    AhatSnapshot snapshot = dump.getAhatSnapshot();
-    Instance referent = (Instance)dump.getDumpedThing("anObject");
-    for (InstanceUtils.NativeAllocation alloc : snapshot.getNativeAllocations()) {
-      if (alloc.referent == referent) {
-        assertEquals(42 , alloc.size);
-        assertEquals(referent.getHeap(), alloc.heap);
-        assertEquals(0xABCDABCD , alloc.pointer);
-        return;
-      }
-    }
-    fail("No native allocation found with anObject as the referent");
-  }
-}
-
diff --git a/tools/ahat/test/ObjectHandlerTest.java b/tools/ahat/test/ObjectHandlerTest.java
new file mode 100644
index 0000000..cd0ba23
--- /dev/null
+++ b/tools/ahat/test/ObjectHandlerTest.java
@@ -0,0 +1,74 @@
+/*
+ * 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.
+ */
+
+package com.android.ahat;
+
+import com.android.ahat.heapdump.AhatInstance;
+import com.android.ahat.heapdump.AhatSnapshot;
+import java.io.IOException;
+import org.junit.Test;
+
+import static org.junit.Assert.assertNotNull;
+
+public class ObjectHandlerTest {
+  @Test
+  public void noCrashClassInstance() throws IOException {
+    TestDump dump = TestDump.getTestDump();
+
+    AhatInstance object = dump.getDumpedAhatInstance("aPhantomReference");
+    assertNotNull(object);
+
+    AhatHandler handler = new ObjectHandler(dump.getAhatSnapshot());
+    TestHandler.testNoCrash(handler, "http://localhost:7100/object?id=" + object.getId());
+  }
+
+  @Test
+  public void noCrashClassObj() throws IOException {
+    TestDump dump = TestDump.getTestDump();
+
+    AhatSnapshot snapshot = dump.getAhatSnapshot();
+    AhatHandler handler = new ObjectHandler(snapshot);
+
+    AhatInstance object = snapshot.findClass("Main");
+    assertNotNull(object);
+
+    TestHandler.testNoCrash(handler, "http://localhost:7100/object?id=" + object.getId());
+  }
+
+  @Test
+  public void noCrashSystemClassObj() throws IOException {
+    TestDump dump = TestDump.getTestDump();
+
+    AhatSnapshot snapshot = dump.getAhatSnapshot();
+    AhatHandler handler = new ObjectHandler(snapshot);
+
+    AhatInstance object = snapshot.findClass("java.lang.String");
+    assertNotNull(object);
+
+    TestHandler.testNoCrash(handler, "http://localhost:7100/object?id=" + object.getId());
+  }
+
+  @Test
+  public void noCrashArrayInstance() throws IOException {
+    TestDump dump = TestDump.getTestDump();
+
+    AhatInstance object = dump.getDumpedAhatInstance("gcPathArray");
+    assertNotNull(object);
+
+    AhatHandler handler = new ObjectHandler(dump.getAhatSnapshot());
+    TestHandler.testNoCrash(handler, "http://localhost:7100/object?id=" + object.getId());
+  }
+}
diff --git a/tools/ahat/test/OverviewHandlerTest.java b/tools/ahat/test/OverviewHandlerTest.java
new file mode 100644
index 0000000..c2f773b
--- /dev/null
+++ b/tools/ahat/test/OverviewHandlerTest.java
@@ -0,0 +1,34 @@
+/*
+ * 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.
+ */
+
+package com.android.ahat;
+
+import com.android.ahat.heapdump.AhatSnapshot;
+import java.io.File;
+import java.io.IOException;
+import org.junit.Test;
+
+public class OverviewHandlerTest {
+
+  @Test
+  public void noCrash() throws IOException {
+    AhatSnapshot snapshot = TestDump.getTestDump().getAhatSnapshot();
+    AhatHandler handler = new OverviewHandler(snapshot,
+        new File("my.hprof.file"),
+        new File("my.base.hprof.file"));
+    TestHandler.testNoCrash(handler, "http://localhost:7100");
+  }
+}
diff --git a/tools/ahat/test/PerformanceTest.java b/tools/ahat/test/PerformanceTest.java
index 6e46800..e13974b 100644
--- a/tools/ahat/test/PerformanceTest.java
+++ b/tools/ahat/test/PerformanceTest.java
@@ -16,13 +16,15 @@
 
 package com.android.ahat;
 
-import com.android.tools.perflib.heap.Instance;
+import com.android.ahat.heapdump.AhatInstance;
+import com.android.ahat.heapdump.AhatSnapshot;
 import java.io.IOException;
 import java.io.OutputStream;
 import java.io.PrintStream;
+import org.junit.Test;
+
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
-import org.junit.Test;
 
 public class PerformanceTest {
   private static class NullOutputStream extends OutputStream {
@@ -36,7 +38,7 @@
     // for any object, including big arrays.
     TestDump dump = TestDump.getTestDump();
 
-    Instance bigArray = (Instance)dump.getDumpedThing("bigArray");
+    AhatInstance bigArray = dump.getDumpedAhatInstance("bigArray");
     assertNotNull(bigArray);
 
     AhatSnapshot snapshot = dump.getAhatSnapshot();
diff --git a/tools/ahat/test/QueryTest.java b/tools/ahat/test/QueryTest.java
index 40e3322..5bcf8ea 100644
--- a/tools/ahat/test/QueryTest.java
+++ b/tools/ahat/test/QueryTest.java
@@ -18,9 +18,10 @@
 
 import java.net.URI;
 import java.net.URISyntaxException;
-import static org.junit.Assert.assertEquals;
 import org.junit.Test;
 
+import static org.junit.Assert.assertEquals;
+
 public class QueryTest {
   @Test
   public void simple() throws URISyntaxException {
diff --git a/tools/ahat/test/RootedHandlerTest.java b/tools/ahat/test/RootedHandlerTest.java
new file mode 100644
index 0000000..f325b8e
--- /dev/null
+++ b/tools/ahat/test/RootedHandlerTest.java
@@ -0,0 +1,30 @@
+/*
+ * 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.
+ */
+
+package com.android.ahat;
+
+import com.android.ahat.heapdump.AhatSnapshot;
+import java.io.IOException;
+import org.junit.Test;
+
+public class RootedHandlerTest {
+  @Test
+  public void noCrash() throws IOException {
+    AhatSnapshot snapshot = TestDump.getTestDump().getAhatSnapshot();
+    AhatHandler handler = new RootedHandler(snapshot);
+    TestHandler.testNoCrash(handler, "http://localhost:7100/rooted");
+  }
+}
diff --git a/tools/ahat/test/SiteHandlerTest.java b/tools/ahat/test/SiteHandlerTest.java
new file mode 100644
index 0000000..37596be
--- /dev/null
+++ b/tools/ahat/test/SiteHandlerTest.java
@@ -0,0 +1,30 @@
+/*
+ * 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.
+ */
+
+package com.android.ahat;
+
+import com.android.ahat.heapdump.AhatSnapshot;
+import java.io.IOException;
+import org.junit.Test;
+
+public class SiteHandlerTest {
+  @Test
+  public void noCrash() throws IOException {
+    AhatSnapshot snapshot = TestDump.getTestDump().getAhatSnapshot();
+    AhatHandler handler = new SiteHandler(snapshot);
+    TestHandler.testNoCrash(handler, "http://localhost:7100/sites");
+  }
+}
diff --git a/tools/ahat/test/SnapshotBuilder.java b/tools/ahat/test/SnapshotBuilder.java
new file mode 100644
index 0000000..0eea635
--- /dev/null
+++ b/tools/ahat/test/SnapshotBuilder.java
@@ -0,0 +1,53 @@
+/*
+ * 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.
+ */
+
+package com.android.ahat;
+
+import com.android.ahat.heapdump.AhatSnapshot;
+import com.android.tools.perflib.heap.ProguardMap;
+import com.android.tools.perflib.heap.hprof.Hprof;
+import com.android.tools.perflib.heap.hprof.HprofRecord;
+import com.android.tools.perflib.heap.hprof.HprofStringBuilder;
+import com.android.tools.perflib.heap.io.InMemoryBuffer;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * Class with utilities to help constructing snapshots for tests.
+ */
+public class SnapshotBuilder {
+
+  // Helper function to make a snapshot with id size 4 given an
+  // HprofStringBuilder and list of HprofRecords
+  public static AhatSnapshot makeSnapshot(HprofStringBuilder strings, List<HprofRecord> records)
+    throws IOException {
+    // TODO: When perflib can handle the case where strings are referred to
+    // before they are defined, just add the string records to the records
+    // list.
+    List<HprofRecord> actualRecords = new ArrayList<HprofRecord>();
+    actualRecords.addAll(strings.getStringRecords());
+    actualRecords.addAll(records);
+
+    Hprof hprof = new Hprof("JAVA PROFILE 1.0.3", 4, new Date(), actualRecords);
+    ByteArrayOutputStream os = new ByteArrayOutputStream();
+    hprof.write(os);
+    InMemoryBuffer buffer = new InMemoryBuffer(os.toByteArray());
+    return AhatSnapshot.fromDataBuffer(buffer, new ProguardMap());
+  }
+}
diff --git a/tools/ahat/test/SortTest.java b/tools/ahat/test/SortTest.java
deleted file mode 100644
index 02ff7db..0000000
--- a/tools/ahat/test/SortTest.java
+++ /dev/null
@@ -1,81 +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.
- */
-
-package com.android.ahat;
-
-import com.android.tools.perflib.heap.ClassObj;
-import com.android.tools.perflib.heap.Heap;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import static org.junit.Assert.assertEquals;
-import org.junit.Test;
-
-public class SortTest {
-  @Test
-  public void objectsInfo() {
-    Heap heapA = new Heap(0xA, "A");
-    Heap heapB = new Heap(0xB, "B");
-    ClassObj classA = new ClassObj(0x1A, null, "classA", 0);
-    ClassObj classB = new ClassObj(0x1B, null, "classB", 0);
-    ClassObj classC = new ClassObj(0x1C, null, "classC", 0);
-    Site.ObjectsInfo infoA = new Site.ObjectsInfo(heapA, classA, 4, 14);
-    Site.ObjectsInfo infoB = new Site.ObjectsInfo(heapB, classB, 2, 15);
-    Site.ObjectsInfo infoC = new Site.ObjectsInfo(heapA, classC, 3, 13);
-    Site.ObjectsInfo infoD = new Site.ObjectsInfo(heapB, classA, 5, 12);
-    Site.ObjectsInfo infoE = new Site.ObjectsInfo(heapA, classB, 1, 11);
-    List<Site.ObjectsInfo> list = new ArrayList<Site.ObjectsInfo>();
-    list.add(infoA);
-    list.add(infoB);
-    list.add(infoC);
-    list.add(infoD);
-    list.add(infoE);
-
-    // Sort by size.
-    Collections.sort(list, new Sort.ObjectsInfoBySize());
-    assertEquals(infoB, list.get(0));
-    assertEquals(infoA, list.get(1));
-    assertEquals(infoC, list.get(2));
-    assertEquals(infoD, list.get(3));
-    assertEquals(infoE, list.get(4));
-
-    // Sort by class name.
-    Collections.sort(list, new Sort.ObjectsInfoByClassName());
-    assertEquals(classA, list.get(0).classObj);
-    assertEquals(classA, list.get(1).classObj);
-    assertEquals(classB, list.get(2).classObj);
-    assertEquals(classB, list.get(3).classObj);
-    assertEquals(classC, list.get(4).classObj);
-
-    // Sort by heap name.
-    Collections.sort(list, new Sort.ObjectsInfoByHeapName());
-    assertEquals(heapA, list.get(0).heap);
-    assertEquals(heapA, list.get(1).heap);
-    assertEquals(heapA, list.get(2).heap);
-    assertEquals(heapB, list.get(3).heap);
-    assertEquals(heapB, list.get(4).heap);
-
-    // Sort first by class name, then by size.
-    Collections.sort(list, new Sort.WithPriority<Site.ObjectsInfo>(
-          new Sort.ObjectsInfoByClassName(),
-          new Sort.ObjectsInfoBySize()));
-    assertEquals(infoA, list.get(0));
-    assertEquals(infoD, list.get(1));
-    assertEquals(infoB, list.get(2));
-    assertEquals(infoE, list.get(3));
-    assertEquals(infoC, list.get(4));
-  }
-}
diff --git a/tools/ahat/test/TestDump.java b/tools/ahat/test/TestDump.java
index ebce61c..ceb7346 100644
--- a/tools/ahat/test/TestDump.java
+++ b/tools/ahat/test/TestDump.java
@@ -16,14 +16,16 @@
 
 package com.android.ahat;
 
-import com.android.tools.perflib.heap.ClassObj;
-import com.android.tools.perflib.heap.Field;
-import com.android.tools.perflib.heap.Instance;
+import com.android.ahat.heapdump.AhatClassObj;
+import com.android.ahat.heapdump.AhatInstance;
+import com.android.ahat.heapdump.AhatSnapshot;
+import com.android.ahat.heapdump.Diff;
+import com.android.ahat.heapdump.FieldValue;
+import com.android.ahat.heapdump.Value;
 import com.android.tools.perflib.heap.ProguardMap;
 import java.io.File;
 import java.io.IOException;
 import java.text.ParseException;
-import java.util.Map;
 
 /**
  * The TestDump class is used to get an AhatSnapshot for the test-dump
@@ -37,30 +39,46 @@
   // is visible to other test cases.
   private static TestDump mCachedTestDump = null;
 
+  // If the test dump fails to load the first time, it will likely fail every
+  // other test we try. Rather than having to wait a potentially very long
+  // time for test dump loading to fail over and over again, record when it
+  // fails and don't try to load it again.
+  private static boolean mTestDumpFailed = false;
+
   private AhatSnapshot mSnapshot = null;
+  private AhatSnapshot mBaseline = null;
 
   /**
-   * Load the test-dump.hprof file.
-   * The location of the file is read from the system property
-   * "ahat.test.dump.hprof", which is expected to be set on the command line.
-   * For example:
-   *   java -Dahat.test.dump.hprof=test-dump.hprof -jar ahat-tests.jar
+   * Load the test-dump.hprof and test-dump-base.hprof files.
+   * The location of the files are read from the system properties
+   * "ahat.test.dump.hprof" and "ahat.test.dump.base.hprof", which is expected
+   * to be set on the command line.
+   * The location of the proguard map for both hprof files is read from the
+   * system property "ahat.test.dump.map".  For example:
+   *   java -Dahat.test.dump.hprof=test-dump.hprof \
+   *        -Dahat.test.dump.base.hprof=test-dump-base.hprof \
+   *        -Dahat.test.dump.map=proguard.map \
+   *        -jar ahat-tests.jar
    *
-   * An IOException is thrown if there is a failure reading the hprof file or
+   * An IOException is thrown if there is a failure reading the hprof files or
    * the proguard map.
    */
   private TestDump() throws IOException {
-      String hprof = System.getProperty("ahat.test.dump.hprof");
+    // TODO: Make use of the baseline hprof for tests.
+    String hprof = System.getProperty("ahat.test.dump.hprof");
+    String hprofBase = System.getProperty("ahat.test.dump.base.hprof");
 
-      String mapfile = System.getProperty("ahat.test.dump.map");
-      ProguardMap map = new ProguardMap();
-      try {
-        map.readFromFile(new File(mapfile));
-      } catch (ParseException e) {
-        throw new IOException("Unable to load proguard map", e);
-      }
+    String mapfile = System.getProperty("ahat.test.dump.map");
+    ProguardMap map = new ProguardMap();
+    try {
+      map.readFromFile(new File(mapfile));
+    } catch (ParseException e) {
+      throw new IOException("Unable to load proguard map", e);
+    }
 
-      mSnapshot = AhatSnapshot.fromHprof(new File(hprof), map);
+    mSnapshot = AhatSnapshot.fromHprof(new File(hprof), map);
+    mBaseline = AhatSnapshot.fromHprof(new File(hprofBase), map);
+    Diff.snapshots(mSnapshot, mBaseline);
   }
 
   /**
@@ -71,18 +89,59 @@
   }
 
   /**
-   * Return the value of a field in the DumpedStuff instance in the
+   * Get the baseline AhatSnapshot for the test dump program.
+   */
+  public AhatSnapshot getBaselineAhatSnapshot() {
+    return mBaseline;
+  }
+
+  /**
+   * Returns the value of a field in the DumpedStuff instance in the
    * snapshot for the test-dump program.
    */
-  public Object getDumpedThing(String name) {
-    ClassObj main = mSnapshot.findClass("Main");
-    Instance stuff = null;
-    for (Map.Entry<Field, Object> fields : main.getStaticFieldValues().entrySet()) {
-      if ("stuff".equals(fields.getKey().getName())) {
-        stuff = (Instance) fields.getValue();
+  public Value getDumpedValue(String name) {
+    return getDumpedValue(name, mSnapshot);
+  }
+
+  /**
+   * Returns the value of a field in the DumpedStuff instance in the
+   * baseline snapshot for the test-dump program.
+   */
+  public Value getBaselineDumpedValue(String name) {
+    return getDumpedValue(name, mBaseline);
+  }
+
+  /**
+   * Returns the value of a field in the DumpedStuff instance in the
+   * given snapshot for the test-dump program.
+   */
+  private Value getDumpedValue(String name, AhatSnapshot snapshot) {
+    AhatClassObj main = snapshot.findClass("Main");
+    AhatInstance stuff = null;
+    for (FieldValue fields : main.getStaticFieldValues()) {
+      if ("stuff".equals(fields.getName())) {
+        stuff = fields.getValue().asAhatInstance();
       }
     }
-    return InstanceUtils.getField(stuff, name);
+    return stuff.getField(name);
+  }
+
+  /**
+   * Returns the value of a non-primitive field in the DumpedStuff instance in
+   * the snapshot for the test-dump program.
+   */
+  public AhatInstance getDumpedAhatInstance(String name) {
+    Value value = getDumpedValue(name);
+    return value == null ? null : value.asAhatInstance();
+  }
+
+  /**
+   * Returns the value of a non-primitive field in the DumpedStuff instance in
+   * the baseline snapshot for the test-dump program.
+   */
+  public AhatInstance getBaselineDumpedAhatInstance(String name) {
+    Value value = getBaselineDumpedValue(name);
+    return value == null ? null : value.asAhatInstance();
   }
 
   /**
@@ -93,8 +152,14 @@
    * when possible.
    */
   public static synchronized TestDump getTestDump() throws IOException {
+    if (mTestDumpFailed) {
+      throw new RuntimeException("Test dump failed before, assuming it will again");
+    }
+
     if (mCachedTestDump == null) {
+      mTestDumpFailed = true;
       mCachedTestDump = new TestDump();
+      mTestDumpFailed = false;
     }
     return mCachedTestDump;
   }
diff --git a/tools/ahat/test/TestHandler.java b/tools/ahat/test/TestHandler.java
new file mode 100644
index 0000000..859e39a
--- /dev/null
+++ b/tools/ahat/test/TestHandler.java
@@ -0,0 +1,41 @@
+/*
+ * 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.
+ */
+
+package com.android.ahat;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintStream;
+
+/**
+ * Provide common utilities for basic handler tests.
+ */
+public class TestHandler {
+  private static class NullOutputStream extends OutputStream {
+    public void write(int b) throws IOException {
+    }
+  }
+
+  /**
+   * Test that the given handler doesn't crash on the given query.
+   */
+  public static void testNoCrash(AhatHandler handler, String uri) throws IOException {
+    PrintStream ps = new PrintStream(new NullOutputStream());
+    HtmlDoc doc = new HtmlDoc(ps, DocString.text("noCrash test"), DocString.uri("style.css"));
+    Query query = new Query(DocString.uri(uri));
+    handler.handle(doc, query);
+  }
+}
diff --git a/tools/ahat/test/Tests.java b/tools/ahat/test/Tests.java
index 3291470..2fd3286 100644
--- a/tools/ahat/test/Tests.java
+++ b/tools/ahat/test/Tests.java
@@ -22,11 +22,14 @@
   public static void main(String[] args) {
     if (args.length == 0) {
       args = new String[]{
-        "com.android.ahat.InstanceUtilsTest",
-        "com.android.ahat.NativeAllocationTest",
+        "com.android.ahat.DiffTest",
+        "com.android.ahat.InstanceTest",
+        "com.android.ahat.ObjectHandlerTest",
+        "com.android.ahat.OverviewHandlerTest",
         "com.android.ahat.PerformanceTest",
+        "com.android.ahat.RootedHandlerTest",
         "com.android.ahat.QueryTest",
-        "com.android.ahat.SortTest",
+        "com.android.ahat.SiteHandlerTest",
       };
     }
     JUnitCore.main(args);
diff --git a/tools/findbuildbotwarnings.py b/tools/findbuildbotwarnings.py
new file mode 100755
index 0000000..a172dd6
--- /dev/null
+++ b/tools/findbuildbotwarnings.py
@@ -0,0 +1,96 @@
+#!/usr/bin/env python
+#
+# 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.
+
+"""Outputs the warnings that are common to all builders.
+
+Suppressed tests that are nonetheless passing are output as warnings
+by vogar.  Any tests that generate warnings in every builder are good
+candidates for no longer being suppressed, since they're passing on
+a regular basis."""
+
+import collections
+import json
+import requests
+
+# The number of recent builds to check for each builder
+NUM_BUILDS = 5
+# The buildbot step to check for warnings
+BUILDBOT_STEP = 'test libcore'
+
+
+def main():
+    # Dict from builder+build_num combination to the list of warnings
+    # in that build
+    warnings = collections.defaultdict(list)
+    r = requests.get('https://build.chromium.org/p/client.art/json/builders')
+    if r.status_code != 200:
+        print r.text
+        return
+    builders = json.loads(r.text)
+    for builder_name in sorted(builders):
+        # Build -1 is the currently-running build (if there is one), so we
+        # start with -2, which should be the most or second-most
+        # recently-completed build.
+        for build_num in range(-2, -2 - NUM_BUILDS, -1):
+            print ('Loading data for %s, build %d...'
+                   % (builder_name, build_num))
+            r = requests.get(
+                'https://build.chromium.org/p/client.art'
+                '/json/builders/%s/builds/%d' % (
+                builder_name, build_num))
+            if r.status_code != 200:
+                print r.text
+                return
+            builder = json.loads(r.text)
+            libcore_steps = [x for x in builder['steps']
+                             if x['name'] == BUILDBOT_STEP]
+            for ls in libcore_steps:
+                stdio_logs = [x for x in ls['logs'] if x[0] == 'stdio']
+                for sl in stdio_logs:
+                    # The default link is HTML, so append /text to get the
+                    # text version
+                    r = requests.get(sl[1] + '/text')
+                    if r.status_code != 200:
+                        print r.text
+                        return
+                    stdio = r.text.splitlines()
+
+                    # Walk from the back of the list to find the start of the
+                    # warnings summary
+                    i = -1
+                    try:
+                        while not stdio[i].startswith('Warnings summary:'):
+                            i -= 1
+                        i += 1   # Ignore the "Warnings summary:" line
+                        while i < -1:
+                            warnings['%s:%d' % (builder_name, build_num)].append(stdio[i])
+                            i += 1
+                    except IndexError:
+                        # Some builds don't have any
+                        print '  No warnings section found.'
+    # sharedwarnings will build up the intersection of all the lists of
+    # warnings.  We seed it with an arbitrary starting point (which is fine
+    # since intersection is commutative).
+    sharedwarnings = set(warnings.popitem()[1])
+    for warning_list in warnings.itervalues():
+        sharedwarnings = sharedwarnings & set(warning_list)
+    print 'Warnings shared across all builders:'
+    for warning in sorted(list(sharedwarnings)):
+        print warning
+
+
+if __name__ == '__main__':
+    main()
diff --git a/tools/libcore_failures.txt b/tools/libcore_failures.txt
index 08abdb3..6529640 100644
--- a/tools/libcore_failures.txt
+++ b/tools/libcore_failures.txt
@@ -105,12 +105,6 @@
   names: ["org.apache.harmony.tests.java.lang.ProcessTest#test_getErrorStream"]
 },
 {
-  description: "Error decoding digital signature bytes.",
-  result: EXEC_FAILED,
-  name: "org.apache.harmony.security.tests.java.security.Signature2Test#test_verify$BII",
-  bug: 18869265
-},
-{
   description: "Test sometimes timeouts on volantis, and on most modes in debug mode",
   result: EXEC_TIMEOUT,
   names: ["libcore.java.lang.SystemTest#testArrayCopyConcurrentModification"],