Merge "ART: Add 16-bit Thumb2 ROR, NEGS and CMP for high registers."
diff --git a/Android.mk b/Android.mk
index 49b61bb..8859d3a 100644
--- a/Android.mk
+++ b/Android.mk
@@ -87,6 +87,7 @@
 include $(art_path)/patchoat/Android.mk
 include $(art_path)/dalvikvm/Android.mk
 include $(art_path)/tools/Android.mk
+include $(art_path)/tools/ahat/Android.mk
 include $(art_path)/tools/dexfuzz/Android.mk
 include $(art_path)/sigchainlib/Android.mk
 
@@ -240,7 +241,7 @@
 
 # Dexdump/list regression test.
 .PHONY: test-art-host-dexdump
-test-art-host-dexdump: $(addprefix $(HOST_OUT_EXECUTABLES)/, dexdump2 dexlist2)
+test-art-host-dexdump: $(addprefix $(HOST_OUT_EXECUTABLES)/, dexdump2 dexlist)
 	ANDROID_HOST_OUT=$(realpath $(HOST_OUT)) art/test/dexdump/run-all-tests
 
 # Valgrind. Currently only 32b gtests.
diff --git a/build/Android.gtest.mk b/build/Android.gtest.mk
index 566d289..c88d677 100644
--- a/build/Android.gtest.mk
+++ b/build/Android.gtest.mk
@@ -106,15 +106,14 @@
   dexdump2
 
 # The dexlist test requires an image and the dexlist utility.
-# TODO: rename into dexlist when migration completes
 ART_GTEST_dexlist_test_HOST_DEPS := \
   $(HOST_CORE_IMAGE_default_no-pic_64) \
   $(HOST_CORE_IMAGE_default_no-pic_32) \
-  $(HOST_OUT_EXECUTABLES)/dexlist2
+  $(HOST_OUT_EXECUTABLES)/dexlist
 ART_GTEST_dexlist_test_TARGET_DEPS := \
   $(TARGET_CORE_IMAGE_default_no-pic_64) \
   $(TARGET_CORE_IMAGE_default_no-pic_32) \
-  dexlist2
+  dexlist
 
 # The imgdiag test has dependencies on core.oat since it needs to load it during the test.
 # For the host, also add the installed tool (in the base size, that should suffice). For the
@@ -247,6 +246,7 @@
   compiler/optimizing/graph_checker_test.cc \
   compiler/optimizing/graph_test.cc \
   compiler/optimizing/gvn_test.cc \
+  compiler/optimizing/induction_var_analysis_test.cc \
   compiler/optimizing/licm_test.cc \
   compiler/optimizing/live_interval_test.cc \
   compiler/optimizing/nodes_test.cc \
diff --git a/compiler/Android.mk b/compiler/Android.mk
index 8b56880..ce9e367 100644
--- a/compiler/Android.mk
+++ b/compiler/Android.mk
@@ -71,6 +71,7 @@
 	optimizing/graph_checker.cc \
 	optimizing/graph_visualizer.cc \
 	optimizing/gvn.cc \
+	optimizing/induction_var_analysis.cc \
 	optimizing/inliner.cc \
 	optimizing/instruction_simplifier.cc \
 	optimizing/intrinsics.cc \
diff --git a/compiler/dex/mir_method_info.cc b/compiler/dex/mir_method_info.cc
index be913fe..31c3808 100644
--- a/compiler/dex/mir_method_info.cc
+++ b/compiler/dex/mir_method_info.cc
@@ -105,7 +105,8 @@
       // Don't devirt if we are in a different dex file since we can't have direct invokes in
       // another dex file unless we always put a direct / patch pointer.
       devirt_target = nullptr;
-      current_dex_cache.Assign(runtime->GetClassLinker()->FindDexCache(*it->target_dex_file_));
+      current_dex_cache.Assign(runtime->GetClassLinker()->FindDexCache(
+          soa.Self(), *it->target_dex_file_));
       CHECK(current_dex_cache.Get() != nullptr);
       DexCompilationUnit cu(
           mUnit->GetCompilationUnit(), mUnit->GetClassLoader(), mUnit->GetClassLinker(),
diff --git a/compiler/dex/quick/quick_compiler.cc b/compiler/dex/quick/quick_compiler.cc
index 6e73ae7..3642b82 100644
--- a/compiler/dex/quick/quick_compiler.cc
+++ b/compiler/dex/quick/quick_compiler.cc
@@ -679,11 +679,8 @@
     return nullptr;
   }
 
-  if (driver->GetVerifiedMethod(&dex_file, method_idx)->HasRuntimeThrow()) {
-    return nullptr;
-  }
-
   DCHECK(driver->GetCompilerOptions().IsCompilationEnabled());
+  DCHECK(!driver->GetVerifiedMethod(&dex_file, method_idx)->HasRuntimeThrow());
 
   Runtime* const runtime = Runtime::Current();
   ClassLinker* const class_linker = runtime->GetClassLinker();
diff --git a/compiler/dex/verified_method.cc b/compiler/dex/verified_method.cc
index 273b1628..8eb37cf 100644
--- a/compiler/dex/verified_method.cc
+++ b/compiler/dex/verified_method.cc
@@ -37,11 +37,21 @@
 
 namespace art {
 
+VerifiedMethod::VerifiedMethod(uint32_t encountered_error_types,
+                               bool has_runtime_throw,
+                               const SafeMap<uint32_t, std::set<uint32_t>>& string_init_pc_reg_map)
+    : encountered_error_types_(encountered_error_types),
+      has_runtime_throw_(has_runtime_throw),
+      string_init_pc_reg_map_(string_init_pc_reg_map) {
+}
+
 const VerifiedMethod* VerifiedMethod::Create(verifier::MethodVerifier* method_verifier,
                                              bool compile) {
-  std::unique_ptr<VerifiedMethod> verified_method(new VerifiedMethod);
-  verified_method->has_verification_failures_ = method_verifier->HasFailures();
-  verified_method->has_runtime_throw_ = method_verifier->HasInstructionThatWillThrow();
+  std::unique_ptr<VerifiedMethod> verified_method(
+      new VerifiedMethod(method_verifier->GetEncounteredFailureTypes(),
+                         method_verifier->HasInstructionThatWillThrow(),
+                         method_verifier->GetStringInitPcRegMap()));
+
   if (compile) {
     /* Generate a register map. */
     if (!verified_method->GenerateGcMap(method_verifier)) {
@@ -66,8 +76,6 @@
     verified_method->GenerateSafeCastSet(method_verifier);
   }
 
-  verified_method->SetStringInitPcRegMap(method_verifier->GetStringInitPcRegMap());
-
   return verified_method.release();
 }
 
diff --git a/compiler/dex/verified_method.h b/compiler/dex/verified_method.h
index f7d6d67..74fcb07 100644
--- a/compiler/dex/verified_method.h
+++ b/compiler/dex/verified_method.h
@@ -72,22 +72,25 @@
 
   // Returns true if there were any errors during verification.
   bool HasVerificationFailures() const {
-    return has_verification_failures_;
+    return encountered_error_types_ != 0;
+  }
+
+  uint32_t GetEncounteredVerificationFailures() const {
+    return encountered_error_types_;
   }
 
   bool HasRuntimeThrow() const {
     return has_runtime_throw_;
   }
 
-  void SetStringInitPcRegMap(SafeMap<uint32_t, std::set<uint32_t>>& string_init_pc_reg_map) {
-    string_init_pc_reg_map_ = string_init_pc_reg_map;
-  }
   const SafeMap<uint32_t, std::set<uint32_t>>& GetStringInitPcRegMap() const {
     return string_init_pc_reg_map_;
   }
 
  private:
-  VerifiedMethod() = default;
+  VerifiedMethod(uint32_t encountered_error_types,
+                 bool has_runtime_throw,
+                 const SafeMap<uint32_t, std::set<uint32_t>>& string_init_pc_reg_map);
 
   /*
    * Generate the GC map for a method that has just been verified (i.e. we're doing this as part of
@@ -124,12 +127,12 @@
   DequickenMap dequicken_map_;
   SafeCastSet safe_cast_set_;
 
-  bool has_verification_failures_ = false;
-  bool has_runtime_throw_ = false;
+  const uint32_t encountered_error_types_;
+  const bool has_runtime_throw_;
 
   // Copy of mapping generated by verifier of dex PCs of string init invocations
   // to the set of other registers that the receiver has been copied into.
-  SafeMap<uint32_t, std::set<uint32_t>> string_init_pc_reg_map_;
+  const SafeMap<uint32_t, std::set<uint32_t>> string_init_pc_reg_map_;
 };
 
 }  // namespace art
diff --git a/compiler/driver/compiler_driver-inl.h b/compiler/driver/compiler_driver-inl.h
index 80387f2..8f1987a 100644
--- a/compiler/driver/compiler_driver-inl.h
+++ b/compiler/driver/compiler_driver-inl.h
@@ -31,7 +31,7 @@
 namespace art {
 
 inline mirror::DexCache* CompilerDriver::GetDexCache(const DexCompilationUnit* mUnit) {
-  return mUnit->GetClassLinker()->FindDexCache(*mUnit->GetDexFile());
+  return mUnit->GetClassLinker()->FindDexCache(Thread::Current(), *mUnit->GetDexFile(), false);
 }
 
 inline mirror::ClassLoader* CompilerDriver::GetClassLoader(ScopedObjectAccess& soa,
@@ -87,7 +87,7 @@
 }
 
 inline mirror::DexCache* CompilerDriver::FindDexCache(const DexFile* dex_file) {
-  return Runtime::Current()->GetClassLinker()->FindDexCache(*dex_file);
+  return Runtime::Current()->GetClassLinker()->FindDexCache(Thread::Current(), *dex_file, false);
 }
 
 inline ArtField* CompilerDriver::ResolveField(
@@ -339,7 +339,8 @@
     // Sharpen a virtual call into a direct call. The method_idx is into referrer's
     // dex cache, check that this resolved method is where we expect it.
     CHECK_EQ(target_method->dex_file, mUnit->GetDexFile());
-    DCHECK_EQ(dex_cache.Get(), mUnit->GetClassLinker()->FindDexCache(*mUnit->GetDexFile()));
+    DCHECK_EQ(dex_cache.Get(), mUnit->GetClassLinker()->FindDexCache(
+        soa.Self(), *mUnit->GetDexFile(), false));
     CHECK_EQ(referrer_class->GetDexCache()->GetResolvedMethod(
         target_method->dex_method_index, pointer_size),
              resolved_method) << PrettyMethod(resolved_method);
@@ -369,7 +370,7 @@
           nullptr, kVirtual);
     } else {
       StackHandleScope<1> hs(soa.Self());
-      auto target_dex_cache(hs.NewHandle(class_linker->FindDexCache(*devirt_target->dex_file)));
+      auto target_dex_cache(hs.NewHandle(class_linker->RegisterDexFile(*devirt_target->dex_file)));
       called_method = class_linker->ResolveMethod(
           *devirt_target->dex_file, devirt_target->dex_method_index, target_dex_cache,
           class_loader, nullptr, kVirtual);
diff --git a/compiler/driver/compiler_driver.cc b/compiler/driver/compiler_driver.cc
index fa25a17..6d3a960 100644
--- a/compiler/driver/compiler_driver.cc
+++ b/compiler/driver/compiler_driver.cc
@@ -590,14 +590,18 @@
   } else if ((access_flags & kAccAbstract) != 0) {
     // Abstract methods don't have code.
   } else {
-    bool has_verified_method = driver->GetVerificationResults()
-        ->GetVerifiedMethod(method_ref) != nullptr;
+    const VerifiedMethod* verified_method =
+        driver->GetVerificationResults()->GetVerifiedMethod(method_ref);
     bool compile = compilation_enabled &&
         // Basic checks, e.g., not <clinit>.
         driver->GetVerificationResults()
             ->IsCandidateForCompilation(method_ref, access_flags) &&
         // Did not fail to create VerifiedMethod metadata.
-        has_verified_method &&
+        verified_method != nullptr &&
+        // Do not have failures that should punt to the interpreter.
+        !verified_method->HasRuntimeThrow() &&
+        (verified_method->GetEncounteredVerificationFailures() &
+            (verifier::VERIFY_ERROR_FORCE_INTERPRETER | verifier::VERIFY_ERROR_LOCKING)) == 0 &&
         // Is eligable for compilation by methods-to-compile filter.
         driver->IsMethodToCompile(method_ref);
     if (compile) {
@@ -620,7 +624,7 @@
           method_idx,
           class_loader,
           dex_file,
-          has_verified_method
+          (verified_method != nullptr)
               ? dex_to_dex_compilation_level
               : optimizer::DexToDexCompilationLevel::kRequired);
     }
@@ -936,7 +940,7 @@
       uint16_t exception_type_idx = exception_type.first;
       const DexFile* dex_file = exception_type.second;
       StackHandleScope<2> hs2(self);
-      Handle<mirror::DexCache> dex_cache(hs2.NewHandle(class_linker->FindDexCache(*dex_file)));
+      Handle<mirror::DexCache> dex_cache(hs2.NewHandle(class_linker->RegisterDexFile(*dex_file)));
       Handle<mirror::Class> klass(hs2.NewHandle(
           class_linker->ResolveType(*dex_file, exception_type_idx, dex_cache,
                                     NullHandle<mirror::ClassLoader>())));
@@ -1170,7 +1174,8 @@
       IsImageClass(dex_file.StringDataByIdx(dex_file.GetTypeId(type_idx).descriptor_idx_))) {
     {
       ScopedObjectAccess soa(Thread::Current());
-      mirror::DexCache* dex_cache = Runtime::Current()->GetClassLinker()->FindDexCache(dex_file);
+      mirror::DexCache* dex_cache = Runtime::Current()->GetClassLinker()->FindDexCache(
+          soa.Self(), dex_file, false);
       mirror::Class* resolved_class = dex_cache->GetResolvedType(type_idx);
       if (resolved_class == nullptr) {
         // Erroneous class.
@@ -1195,9 +1200,10 @@
     // We resolve all const-string strings when building for the image.
     ScopedObjectAccess soa(Thread::Current());
     StackHandleScope<1> hs(soa.Self());
-    Handle<mirror::DexCache> dex_cache(
-        hs.NewHandle(Runtime::Current()->GetClassLinker()->FindDexCache(dex_file)));
-    Runtime::Current()->GetClassLinker()->ResolveString(dex_file, string_idx, dex_cache);
+    ClassLinker* const class_linker = Runtime::Current()->GetClassLinker();
+    Handle<mirror::DexCache> dex_cache(hs.NewHandle(class_linker->FindDexCache(
+        soa.Self(), dex_file, false)));
+    class_linker->ResolveString(dex_file, string_idx, dex_cache);
     result = true;
   }
   if (result) {
@@ -1222,7 +1228,8 @@
     *equals_referrers_class = false;
   }
   ScopedObjectAccess soa(Thread::Current());
-  mirror::DexCache* dex_cache = Runtime::Current()->GetClassLinker()->FindDexCache(dex_file);
+  mirror::DexCache* dex_cache = Runtime::Current()->GetClassLinker()->FindDexCache(
+      soa.Self(), dex_file, false);
   // 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) {
@@ -1259,7 +1266,8 @@
                                                             const DexFile& dex_file,
                                                             uint32_t type_idx) {
   ScopedObjectAccess soa(Thread::Current());
-  mirror::DexCache* dex_cache = Runtime::Current()->GetClassLinker()->FindDexCache(dex_file);
+  mirror::DexCache* dex_cache = Runtime::Current()->GetClassLinker()->FindDexCache(
+      soa.Self(), dex_file, false);
   // 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) {
@@ -1288,7 +1296,8 @@
                                         uintptr_t* direct_type_ptr, bool* out_is_finalizable) {
   ScopedObjectAccess soa(Thread::Current());
   Runtime* runtime = Runtime::Current();
-  mirror::DexCache* dex_cache = runtime->GetClassLinker()->FindDexCache(dex_file);
+  mirror::DexCache* dex_cache = runtime->GetClassLinker()->FindDexCache(
+      soa.Self(), dex_file, false);
   mirror::Class* resolved_class = dex_cache->GetResolvedType(type_idx);
   if (resolved_class == nullptr) {
     return false;
@@ -1417,7 +1426,8 @@
   {
     StackHandleScope<2> hs(soa.Self());
     Handle<mirror::DexCache> dex_cache_handle(
-        hs.NewHandle(mUnit->GetClassLinker()->FindDexCache(*mUnit->GetDexFile())));
+        hs.NewHandle(mUnit->GetClassLinker()->FindDexCache(
+            soa.Self(), *mUnit->GetDexFile(), false)));
     Handle<mirror::ClassLoader> class_loader_handle(
         hs.NewHandle(soa.Decode<mirror::ClassLoader*>(mUnit->GetClassLoader())));
     resolved_field =
@@ -1467,7 +1477,8 @@
   {
     StackHandleScope<2> hs(soa.Self());
     Handle<mirror::DexCache> dex_cache_handle(
-        hs.NewHandle(mUnit->GetClassLinker()->FindDexCache(*mUnit->GetDexFile())));
+        hs.NewHandle(mUnit->GetClassLinker()->FindDexCache(
+            soa.Self(), *mUnit->GetDexFile(), false)));
     Handle<mirror::ClassLoader> class_loader_handle(
         hs.NewHandle(soa.Decode<mirror::ClassLoader*>(mUnit->GetClassLoader())));
     resolved_field =
@@ -1653,7 +1664,8 @@
   // Try to resolve the method and compiling method's class.
   StackHandleScope<3> hs(soa.Self());
   Handle<mirror::DexCache> dex_cache(
-      hs.NewHandle(mUnit->GetClassLinker()->FindDexCache(*mUnit->GetDexFile())));
+      hs.NewHandle(mUnit->GetClassLinker()->FindDexCache(
+          soa.Self(), *mUnit->GetDexFile(), false)));
   Handle<mirror::ClassLoader> class_loader(hs.NewHandle(
       soa.Decode<mirror::ClassLoader*>(mUnit->GetClassLoader())));
   uint32_t method_idx = target_method->dex_method_index;
@@ -1905,7 +1917,8 @@
     StackHandleScope<2> hs(soa.Self());
     Handle<mirror::ClassLoader> class_loader(
         hs.NewHandle(soa.Decode<mirror::ClassLoader*>(jclass_loader)));
-    Handle<mirror::DexCache> dex_cache(hs.NewHandle(class_linker->FindDexCache(dex_file)));
+    Handle<mirror::DexCache> dex_cache(hs.NewHandle(class_linker->FindDexCache(
+        soa.Self(), dex_file, false)));
     // Resolve the class.
     mirror::Class* klass = class_linker->ResolveType(dex_file, class_def.class_idx_, dex_cache,
                                                      class_loader);
@@ -1998,7 +2011,7 @@
     ClassLinker* class_linker = manager_->GetClassLinker();
     const DexFile& dex_file = *manager_->GetDexFile();
     StackHandleScope<2> hs(soa.Self());
-    Handle<mirror::DexCache> dex_cache(hs.NewHandle(class_linker->FindDexCache(dex_file)));
+    Handle<mirror::DexCache> dex_cache(hs.NewHandle(class_linker->RegisterDexFile(dex_file)));
     Handle<mirror::ClassLoader> class_loader(
         hs.NewHandle(soa.Decode<mirror::ClassLoader*>(manager_->GetClassLoader())));
     mirror::Class* klass = class_linker->ResolveType(dex_file, type_idx, dex_cache, class_loader);
@@ -2084,7 +2097,8 @@
        * This is to ensure the class is structurally sound for compilation. An unsound class
        * will be rejected by the verifier and later skipped during compilation in the compiler.
        */
-      Handle<mirror::DexCache> dex_cache(hs.NewHandle(class_linker->FindDexCache(dex_file)));
+      Handle<mirror::DexCache> dex_cache(hs.NewHandle(class_linker->FindDexCache(
+          soa.Self(), dex_file, false)));
       std::string error_msg;
       if (verifier::MethodVerifier::VerifyClass(soa.Self(), &dex_file, dex_cache, class_loader,
                                                 &class_def, true, &error_msg) ==
diff --git a/compiler/driver/compiler_driver_test.cc b/compiler/driver/compiler_driver_test.cc
index e35d07d..1107599 100644
--- a/compiler/driver/compiler_driver_test.cc
+++ b/compiler/driver/compiler_driver_test.cc
@@ -108,7 +108,7 @@
   ScopedObjectAccess soa(Thread::Current());
   ASSERT_TRUE(java_lang_dex_file_ != nullptr);
   const DexFile& dex = *java_lang_dex_file_;
-  mirror::DexCache* dex_cache = class_linker_->FindDexCache(dex);
+  mirror::DexCache* dex_cache = class_linker_->FindDexCache(soa.Self(), dex);
   EXPECT_EQ(dex.NumStringIds(), dex_cache->NumStrings());
   for (size_t i = 0; i < dex_cache->NumStrings(); i++) {
     const mirror::String* string = dex_cache->GetResolvedString(i);
@@ -210,8 +210,8 @@
   CompileAll(class_loader);
 
   ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
-  StackHandleScope<1> hs(self);
   ScopedObjectAccess soa(self);
+  StackHandleScope<1> hs(self);
   Handle<mirror::ClassLoader> h_loader(hs.NewHandle(
       reinterpret_cast<mirror::ClassLoader*>(self->DecodeJObject(class_loader))));
   mirror::Class* klass = class_linker->FindClass(self, "LStaticLeafMethods;", h_loader);
diff --git a/compiler/image_writer.cc b/compiler/image_writer.cc
index 93897aa..dbd3366 100644
--- a/compiler/image_writer.cc
+++ b/compiler/image_writer.cc
@@ -70,7 +70,6 @@
 
 // Separate objects into multiple bins to optimize dirty memory use.
 static constexpr bool kBinObjects = true;
-static constexpr bool kComputeEagerResolvedStrings = false;
 
 static void CheckNoDexObjectsCallback(Object* obj, void* arg ATTRIBUTE_UNUSED)
     SHARED_REQUIRES(Locks::mutator_lock_) {
@@ -90,11 +89,6 @@
     PruneNonImageClasses();  // Remove junk
     ComputeLazyFieldsForImageClasses();  // Add useful information
 
-    // Calling this can in theory fill in some resolved strings. However, in practice it seems to
-    // never resolve any.
-    if (kComputeEagerResolvedStrings) {
-      ComputeEagerResolvedStrings();
-    }
     Thread::Current()->TransitionFromRunnableToSuspended(kNative);
   }
   gc::Heap* heap = Runtime::Current()->GetHeap();
@@ -302,11 +296,15 @@
 
 void ImageWriter::PrepareDexCacheArraySlots() {
   ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
-  ReaderMutexLock mu(Thread::Current(), *class_linker->DexLock());
-  size_t dex_cache_count = class_linker->GetDexCacheCount();
+  Thread* const self = Thread::Current();
+  ReaderMutexLock mu(self, *class_linker->DexLock());
   uint32_t size = 0u;
-  for (size_t idx = 0; idx < dex_cache_count; ++idx) {
-    DexCache* dex_cache = class_linker->GetDexCache(idx);
+  for (jobject weak_root : class_linker->GetDexCaches()) {
+    mirror::DexCache* dex_cache =
+        down_cast<mirror::DexCache*>(self->DecodeJObject(weak_root));
+    if (dex_cache == nullptr) {
+      continue;
+    }
     const DexFile* dex_file = dex_cache->GetDexFile();
     dex_cache_array_starts_.Put(dex_file, size);
     DexCacheArraysLayout layout(target_ptr_size_, dex_file);
@@ -554,39 +552,6 @@
   class_linker->VisitClassesWithoutClassesLock(&visitor);
 }
 
-void ImageWriter::ComputeEagerResolvedStringsCallback(Object* obj, void* arg ATTRIBUTE_UNUSED) {
-  if (!obj->GetClass()->IsStringClass()) {
-    return;
-  }
-  mirror::String* string = obj->AsString();
-  const uint16_t* utf16_string = string->GetValue();
-  size_t utf16_length = static_cast<size_t>(string->GetLength());
-  ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
-  ReaderMutexLock mu(Thread::Current(), *class_linker->DexLock());
-  size_t dex_cache_count = class_linker->GetDexCacheCount();
-  for (size_t i = 0; i < dex_cache_count; ++i) {
-    DexCache* dex_cache = class_linker->GetDexCache(i);
-    const DexFile& dex_file = *dex_cache->GetDexFile();
-    const DexFile::StringId* string_id;
-    if (UNLIKELY(utf16_length == 0)) {
-      string_id = dex_file.FindStringId("");
-    } else {
-      string_id = dex_file.FindStringId(utf16_string, utf16_length);
-    }
-    if (string_id != nullptr) {
-      // This string occurs in this dex file, assign the dex cache entry.
-      uint32_t string_idx = dex_file.GetIndexForStringId(*string_id);
-      if (dex_cache->GetResolvedString(string_idx) == nullptr) {
-        dex_cache->SetResolvedString(string_idx, string);
-      }
-    }
-  }
-}
-
-void ImageWriter::ComputeEagerResolvedStrings() {
-  Runtime::Current()->GetHeap()->VisitObjects(ComputeEagerResolvedStringsCallback, this);
-}
-
 bool ImageWriter::IsImageClass(Class* klass) {
   if (klass == nullptr) {
     return false;
@@ -631,16 +596,14 @@
 
   // Clear references to removed classes from the DexCaches.
   const ArtMethod* resolution_method = runtime->GetResolutionMethod();
-  size_t dex_cache_count;
-  {
-    ReaderMutexLock mu(self, *class_linker->DexLock());
-    dex_cache_count = class_linker->GetDexCacheCount();
-  }
-  for (size_t idx = 0; idx < dex_cache_count; ++idx) {
-    DexCache* dex_cache;
-    {
-      ReaderMutexLock mu(self, *class_linker->DexLock());
-      dex_cache = class_linker->GetDexCache(idx);
+
+  ScopedAssertNoThreadSuspension sa(self, __FUNCTION__);
+  ReaderMutexLock mu(self, *Locks::classlinker_classes_lock_);  // For ClassInClassTable
+  ReaderMutexLock mu2(self, *class_linker->DexLock());
+  for (jobject weak_root : class_linker->GetDexCaches()) {
+    mirror::DexCache* dex_cache = down_cast<mirror::DexCache*>(self->DecodeJObject(weak_root));
+    if (dex_cache == nullptr) {
+      continue;
     }
     for (size_t i = 0; i < dex_cache->NumResolvedTypes(); i++) {
       Class* klass = dex_cache->GetResolvedType(i);
@@ -762,8 +725,12 @@
     ReaderMutexLock mu(self, *class_linker->DexLock());
     CHECK_EQ(dex_cache_count, class_linker->GetDexCacheCount())
         << "The number of dex caches changed.";
-    for (size_t i = 0; i < dex_cache_count; ++i) {
-      dex_caches->Set<false>(i, class_linker->GetDexCache(i));
+    size_t i = 0;
+    for (jobject weak_root : class_linker->GetDexCaches()) {
+      mirror::DexCache* dex_cache =
+          down_cast<mirror::DexCache*>(self->DecodeJObject(weak_root));
+      dex_caches->Set<false>(i, dex_cache);
+      ++i;
     }
   }
 
diff --git a/compiler/image_writer.h b/compiler/image_writer.h
index c8aa82d..778521c 100644
--- a/compiler/image_writer.h
+++ b/compiler/image_writer.h
@@ -225,11 +225,6 @@
   void ComputeLazyFieldsForImageClasses()
       SHARED_REQUIRES(Locks::mutator_lock_);
 
-  // Wire dex cache resolved strings to strings in the image to avoid runtime resolution.
-  void ComputeEagerResolvedStrings() SHARED_REQUIRES(Locks::mutator_lock_);
-  static void ComputeEagerResolvedStringsCallback(mirror::Object* obj, void* arg)
-      SHARED_REQUIRES(Locks::mutator_lock_);
-
   // Remove unwanted classes from various roots.
   void PruneNonImageClasses() SHARED_REQUIRES(Locks::mutator_lock_);
 
diff --git a/compiler/oat_writer.cc b/compiler/oat_writer.cc
index 64e7487..fdf904d 100644
--- a/compiler/oat_writer.cc
+++ b/compiler/oat_writer.cc
@@ -617,7 +617,8 @@
     // Unchecked as we hold mutator_lock_ on entry.
     ScopedObjectAccessUnchecked soa(Thread::Current());
     StackHandleScope<1> hs(soa.Self());
-    Handle<mirror::DexCache> dex_cache(hs.NewHandle(linker->FindDexCache(*dex_file_)));
+    Handle<mirror::DexCache> dex_cache(hs.NewHandle(linker->FindDexCache(
+        Thread::Current(), *dex_file_)));
     ArtMethod* method = linker->ResolveMethod(
         *dex_file_, it.GetMemberIndex(), dex_cache, NullHandle<mirror::ClassLoader>(), nullptr,
         invoke_type);
@@ -668,7 +669,7 @@
       SHARED_REQUIRES(Locks::mutator_lock_) {
     OatDexMethodVisitor::StartClass(dex_file, class_def_index);
     if (dex_cache_ == nullptr || dex_cache_->GetDexFile() != dex_file) {
-      dex_cache_ = class_linker_->FindDexCache(*dex_file);
+      dex_cache_ = class_linker_->FindDexCache(Thread::Current(), *dex_file);
     }
     return true;
   }
@@ -691,6 +692,8 @@
     OatClass* oat_class = writer_->oat_classes_[oat_class_index_];
     const CompiledMethod* compiled_method = oat_class->GetCompiledMethod(class_def_method_index);
 
+    // No thread suspension since dex_cache_ that may get invalidated if that occurs.
+    ScopedAssertNoThreadSuspension tsc(Thread::Current(), __FUNCTION__);
     if (compiled_method != nullptr) {  // ie. not an abstract method
       size_t file_offset = file_offset_;
       OutputStream* out = out_;
@@ -796,7 +799,8 @@
       SHARED_REQUIRES(Locks::mutator_lock_) {
     MethodReference ref = patch.TargetMethod();
     mirror::DexCache* dex_cache =
-        (dex_file_ == ref.dex_file) ? dex_cache_ : class_linker_->FindDexCache(*ref.dex_file);
+        (dex_file_ == ref.dex_file) ? dex_cache_ : class_linker_->FindDexCache(
+            Thread::Current(), *ref.dex_file);
     ArtMethod* method = dex_cache->GetResolvedMethod(
         ref.dex_method_index, class_linker_->GetImagePointerSize());
     CHECK(method != nullptr);
@@ -830,7 +834,7 @@
   mirror::Class* GetTargetType(const LinkerPatch& patch)
       SHARED_REQUIRES(Locks::mutator_lock_) {
     mirror::DexCache* dex_cache = (dex_file_ == patch.TargetTypeDexFile())
-        ? dex_cache_ : class_linker_->FindDexCache(*patch.TargetTypeDexFile());
+        ? dex_cache_ : class_linker_->FindDexCache(Thread::Current(), *patch.TargetTypeDexFile());
     mirror::Class* type = dex_cache->GetResolvedType(patch.TargetTypeIndex());
     CHECK(type != nullptr);
     return type;
diff --git a/compiler/optimizing/builder.cc b/compiler/optimizing/builder.cc
index 7b42db8..23ab94e 100644
--- a/compiler/optimizing/builder.cc
+++ b/compiler/optimizing/builder.cc
@@ -902,7 +902,7 @@
   StackHandleScope<4> hs(soa.Self());
   Handle<mirror::DexCache> dex_cache(hs.NewHandle(
       dex_compilation_unit_->GetClassLinker()->FindDexCache(
-          *dex_compilation_unit_->GetDexFile())));
+          soa.Self(), *dex_compilation_unit_->GetDexFile())));
   Handle<mirror::ClassLoader> class_loader(hs.NewHandle(
       soa.Decode<mirror::ClassLoader*>(dex_compilation_unit_->GetClassLoader())));
   ArtMethod* resolved_method = compiler_driver_->ResolveMethod(
@@ -912,7 +912,7 @@
 
   const DexFile& outer_dex_file = *outer_compilation_unit_->GetDexFile();
   Handle<mirror::DexCache> outer_dex_cache(hs.NewHandle(
-      outer_compilation_unit_->GetClassLinker()->FindDexCache(outer_dex_file)));
+      outer_compilation_unit_->GetClassLinker()->FindDexCache(soa.Self(), outer_dex_file)));
   Handle<mirror::Class> outer_class(hs.NewHandle(GetOutermostCompilingClass()));
 
   // The index at which the method's class is stored in the DexCache's type array.
@@ -1228,7 +1228,7 @@
   Handle<mirror::ClassLoader> class_loader(hs.NewHandle(
       soa.Decode<mirror::ClassLoader*>(compilation_unit.GetClassLoader())));
   Handle<mirror::DexCache> dex_cache(hs.NewHandle(
-      compilation_unit.GetClassLinker()->FindDexCache(dex_file)));
+      compilation_unit.GetClassLinker()->FindDexCache(soa.Self(), dex_file)));
 
   return driver->ResolveCompilingMethodsClass(soa, dex_cache, class_loader, &compilation_unit);
 }
@@ -1245,7 +1245,8 @@
   ScopedObjectAccess soa(Thread::Current());
   StackHandleScope<4> hs(soa.Self());
   Handle<mirror::DexCache> dex_cache(hs.NewHandle(
-      dex_compilation_unit_->GetClassLinker()->FindDexCache(*dex_compilation_unit_->GetDexFile())));
+      dex_compilation_unit_->GetClassLinker()->FindDexCache(
+          soa.Self(), *dex_compilation_unit_->GetDexFile())));
   Handle<mirror::ClassLoader> class_loader(hs.NewHandle(
       soa.Decode<mirror::ClassLoader*>(dex_compilation_unit_->GetClassLoader())));
   Handle<mirror::Class> cls(hs.NewHandle(compiler_driver_->ResolveClass(
@@ -1264,7 +1265,8 @@
   ScopedObjectAccess soa(Thread::Current());
   StackHandleScope<4> hs(soa.Self());
   Handle<mirror::DexCache> dex_cache(hs.NewHandle(
-      dex_compilation_unit_->GetClassLinker()->FindDexCache(*dex_compilation_unit_->GetDexFile())));
+      dex_compilation_unit_->GetClassLinker()->FindDexCache(
+          soa.Self(), *dex_compilation_unit_->GetDexFile())));
   Handle<mirror::ClassLoader> class_loader(hs.NewHandle(
       soa.Decode<mirror::ClassLoader*>(dex_compilation_unit_->GetClassLoader())));
   ArtField* resolved_field = compiler_driver_->ResolveField(
@@ -1277,7 +1279,7 @@
 
   const DexFile& outer_dex_file = *outer_compilation_unit_->GetDexFile();
   Handle<mirror::DexCache> outer_dex_cache(hs.NewHandle(
-      outer_compilation_unit_->GetClassLinker()->FindDexCache(outer_dex_file)));
+      outer_compilation_unit_->GetClassLinker()->FindDexCache(soa.Self(), outer_dex_file)));
   Handle<mirror::Class> outer_class(hs.NewHandle(GetOutermostCompilingClass()));
 
   // The index at which the field's class is stored in the DexCache's type array.
diff --git a/compiler/optimizing/induction_var_analysis.cc b/compiler/optimizing/induction_var_analysis.cc
new file mode 100644
index 0000000..8aaec68
--- /dev/null
+++ b/compiler/optimizing/induction_var_analysis.cc
@@ -0,0 +1,479 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "induction_var_analysis.h"
+
+namespace art {
+
+/**
+ * Returns true if instruction is invariant within the given loop.
+ */
+static bool IsLoopInvariant(HLoopInformation* loop, HInstruction* instruction) {
+  HLoopInformation* other_loop = instruction->GetBlock()->GetLoopInformation();
+  if (other_loop != loop) {
+    // If instruction does not occur in same loop, it is invariant
+    // if it appears in an outer loop (including no loop at all).
+    return other_loop == nullptr || loop->IsIn(*other_loop);
+  }
+  return false;
+}
+
+/**
+ * Returns true if instruction is proper entry-phi-operation for given loop
+ * (referred to as mu-operation in Gerlek's paper).
+ */
+static bool IsEntryPhi(HLoopInformation* loop, HInstruction* instruction) {
+  return
+      instruction->IsPhi() &&
+      instruction->InputCount() == 2 &&
+      instruction->GetBlock() == loop->GetHeader();
+}
+
+//
+// Class methods.
+//
+
+HInductionVarAnalysis::HInductionVarAnalysis(HGraph* graph)
+    : HOptimization(graph, kInductionPassName),
+      global_depth_(0),
+      stack_(graph->GetArena()->Adapter()),
+      scc_(graph->GetArena()->Adapter()),
+      map_(std::less<int>(), graph->GetArena()->Adapter()),
+      cycle_(std::less<int>(), graph->GetArena()->Adapter()),
+      induction_(std::less<int>(), graph->GetArena()->Adapter()) {
+}
+
+void HInductionVarAnalysis::Run() {
+  // Detects sequence variables (generalized induction variables) during an
+  // inner-loop-first traversal of all loops using Gerlek's algorithm.
+  for (HPostOrderIterator it_graph(*graph_); !it_graph.Done(); it_graph.Advance()) {
+    HBasicBlock* graph_block = it_graph.Current();
+    if (graph_block->IsLoopHeader()) {
+      VisitLoop(graph_block->GetLoopInformation());
+    }
+  }
+}
+
+void HInductionVarAnalysis::VisitLoop(HLoopInformation* loop) {
+  // Find strongly connected components (SSCs) in the SSA graph of this loop using Tarjan's
+  // algorithm. Due to the descendant-first nature, classification happens "on-demand".
+  global_depth_ = 0;
+  CHECK(stack_.empty());
+  map_.clear();
+
+  for (HBlocksInLoopIterator it_loop(*loop); !it_loop.Done(); it_loop.Advance()) {
+    HBasicBlock* loop_block = it_loop.Current();
+    CHECK(loop_block->IsInLoop());
+    if (loop_block->GetLoopInformation() != loop) {
+      continue;  // Inner loops already visited.
+    }
+    // Visit phi-operations and instructions.
+    for (HInstructionIterator it(loop_block->GetPhis()); !it.Done(); it.Advance()) {
+      HInstruction* instruction = it.Current();
+      if (!IsVisitedNode(instruction->GetId())) {
+        VisitNode(loop, instruction);
+      }
+    }
+    for (HInstructionIterator it(loop_block->GetInstructions()); !it.Done(); it.Advance()) {
+      HInstruction* instruction = it.Current();
+      if (!IsVisitedNode(instruction->GetId())) {
+        VisitNode(loop, instruction);
+      }
+    }
+  }
+
+  CHECK(stack_.empty());
+  map_.clear();
+}
+
+void HInductionVarAnalysis::VisitNode(HLoopInformation* loop, HInstruction* instruction) {
+  const int id = instruction->GetId();
+  const uint32_t d1 = ++global_depth_;
+  map_.Put(id, NodeInfo(d1));
+  stack_.push_back(instruction);
+
+  // Visit all descendants.
+  uint32_t low = d1;
+  for (size_t i = 0, count = instruction->InputCount(); i < count; ++i) {
+    low = std::min(low, VisitDescendant(loop, instruction->InputAt(i)));
+  }
+
+  // Lower or found SCC?
+  if (low < d1) {
+    map_.find(id)->second.depth = low;
+  } else {
+    scc_.clear();
+    cycle_.clear();
+
+    // Pop the stack to build the SCC for classification.
+    while (!stack_.empty()) {
+      HInstruction* x = stack_.back();
+      scc_.push_back(x);
+      stack_.pop_back();
+      map_.find(x->GetId())->second.done = true;
+      if (x == instruction) {
+        break;
+      }
+    }
+
+    // Classify the SCC.
+    if (scc_.size() == 1 && !IsEntryPhi(loop, scc_[0])) {
+      ClassifyTrivial(loop, scc_[0]);
+    } else {
+      ClassifyNonTrivial(loop);
+    }
+
+    scc_.clear();
+    cycle_.clear();
+  }
+}
+
+uint32_t HInductionVarAnalysis::VisitDescendant(HLoopInformation* loop, HInstruction* instruction) {
+  // If the definition is either outside the loop (loop invariant entry value)
+  // or assigned in inner loop (inner exit value), the traversal stops.
+  HLoopInformation* otherLoop = instruction->GetBlock()->GetLoopInformation();
+  if (otherLoop != loop) {
+    return global_depth_;
+  }
+
+  // Inspect descendant node.
+  const int id = instruction->GetId();
+  if (!IsVisitedNode(id)) {
+    VisitNode(loop, instruction);
+    return map_.find(id)->second.depth;
+  } else {
+    auto it = map_.find(id);
+    return it->second.done ? global_depth_ : it->second.depth;
+  }
+}
+
+void HInductionVarAnalysis::ClassifyTrivial(HLoopInformation* loop, HInstruction* instruction) {
+  InductionInfo* info = nullptr;
+  if (instruction->IsPhi()) {
+    for (size_t i = 1, count = instruction->InputCount(); i < count; i++) {
+      info = TransferPhi(LookupInfo(loop, instruction->InputAt(0)),
+                         LookupInfo(loop, instruction->InputAt(i)));
+    }
+  } else if (instruction->IsAdd()) {
+    info = TransferAddSub(LookupInfo(loop, instruction->InputAt(0)),
+                          LookupInfo(loop, instruction->InputAt(1)), kAdd);
+  } else if (instruction->IsSub()) {
+    info = TransferAddSub(LookupInfo(loop, instruction->InputAt(0)),
+                          LookupInfo(loop, instruction->InputAt(1)), kSub);
+  } else if (instruction->IsMul()) {
+    info = TransferMul(LookupInfo(loop, instruction->InputAt(0)),
+                       LookupInfo(loop, instruction->InputAt(1)));
+  } else if (instruction->IsNeg()) {
+    info = TransferNeg(LookupInfo(loop, instruction->InputAt(0)));
+  }
+
+  // Successfully classified?
+  if (info != nullptr) {
+    AssignInfo(loop, instruction, info);
+  }
+}
+
+void HInductionVarAnalysis::ClassifyNonTrivial(HLoopInformation* loop) {
+  const size_t size = scc_.size();
+  CHECK_GE(size, 1u);
+  HInstruction* phi = scc_[size - 1];
+  if (!IsEntryPhi(loop, phi)) {
+    return;
+  }
+  HInstruction* external = phi->InputAt(0);
+  HInstruction* internal = phi->InputAt(1);
+  InductionInfo* initial = LookupInfo(loop, external);
+  if (initial == nullptr || initial->induction_class != kInvariant) {
+    return;
+  }
+
+  // Singleton entry-phi-operation may be a wrap-around induction.
+  if (size == 1) {
+    InductionInfo* update = LookupInfo(loop, internal);
+    if (update != nullptr) {
+      AssignInfo(loop, phi, NewInductionInfo(kWrapAround, kNop, initial, update, nullptr));
+    }
+    return;
+  }
+
+  // Inspect remainder of the cycle that resides in scc_. The cycle_ mapping assigns
+  // temporary meaning to its nodes.
+  cycle_.Overwrite(phi->GetId(), nullptr);
+  for (size_t i = 0; i < size - 1; i++) {
+    HInstruction* operation = scc_[i];
+    InductionInfo* update = nullptr;
+    if (operation->IsPhi()) {
+      update = TransferCycleOverPhi(operation);
+    } else if (operation->IsAdd()) {
+      update = TransferCycleOverAddSub(loop, operation->InputAt(0), operation->InputAt(1), kAdd, true);
+    } else if (operation->IsSub()) {
+      update = TransferCycleOverAddSub(loop, operation->InputAt(0), operation->InputAt(1), kSub, true);
+    }
+    if (update == nullptr) {
+      return;
+    }
+    cycle_.Overwrite(operation->GetId(), update);
+  }
+
+  // Success if the internal link received accumulated nonzero update.
+  auto it = cycle_.find(internal->GetId());
+  if (it != cycle_.end() && it->second != nullptr) {
+    // Classify header phi and feed the cycle "on-demand".
+    AssignInfo(loop, phi, NewInductionInfo(kLinear, kNop, it->second, initial, nullptr));
+    for (size_t i = 0; i < size - 1; i++) {
+      ClassifyTrivial(loop, scc_[i]);
+    }
+  }
+}
+
+HInductionVarAnalysis::InductionInfo* HInductionVarAnalysis::TransferPhi(InductionInfo* a,
+                                                                         InductionInfo* b) {
+  // Transfer over a phi: if both inputs are identical, result is input.
+  if (InductionEqual(a, b)) {
+    return a;
+  }
+  return nullptr;
+}
+
+HInductionVarAnalysis::InductionInfo* HInductionVarAnalysis::TransferAddSub(InductionInfo* a,
+                                                                            InductionInfo* b,
+                                                                            InductionOp op) {
+  // Transfer over an addition or subtraction: invariant or linear
+  // inputs combine into new invariant or linear result.
+  if (a != nullptr && b != nullptr) {
+    if (a->induction_class == kInvariant && b->induction_class == kInvariant) {
+      return NewInductionInfo(kInvariant, op, a, b, nullptr);
+    } else if (a->induction_class == kLinear && b->induction_class == kInvariant) {
+      return NewInductionInfo(
+          kLinear,
+          kNop,
+          a->op_a,
+          NewInductionInfo(kInvariant, op, a->op_b, b, nullptr),
+          nullptr);
+    } else if (a->induction_class == kInvariant && b->induction_class == kLinear) {
+      InductionInfo* ba = b->op_a;
+      if (op == kSub) {  // negation required
+        ba = NewInductionInfo(kInvariant, kNeg, nullptr, ba, nullptr);
+      }
+      return NewInductionInfo(
+          kLinear,
+          kNop,
+          ba,
+          NewInductionInfo(kInvariant, op, a, b->op_b, nullptr),
+          nullptr);
+    } else if (a->induction_class == kLinear && b->induction_class == kLinear) {
+      return NewInductionInfo(
+          kLinear,
+          kNop,
+          NewInductionInfo(kInvariant, op, a->op_a, b->op_a, nullptr),
+          NewInductionInfo(kInvariant, op, a->op_b, b->op_b, nullptr),
+          nullptr);
+    }
+  }
+  return nullptr;
+}
+
+HInductionVarAnalysis::InductionInfo* HInductionVarAnalysis::TransferMul(InductionInfo* a,
+                                                                         InductionInfo* b) {
+  // Transfer over a multiplication: invariant or linear
+  // inputs combine into new invariant or linear result.
+  // Two linear inputs would become quadratic.
+  if (a != nullptr && b != nullptr) {
+    if (a->induction_class == kInvariant && b->induction_class == kInvariant) {
+      return NewInductionInfo(kInvariant, kMul, a, b, nullptr);
+    } else if (a->induction_class == kLinear && b->induction_class == kInvariant) {
+      return NewInductionInfo(
+          kLinear,
+          kNop,
+          NewInductionInfo(kInvariant, kMul, a->op_a, b, nullptr),
+          NewInductionInfo(kInvariant, kMul, a->op_b, b, nullptr),
+          nullptr);
+    } else if (a->induction_class == kInvariant && b->induction_class == kLinear) {
+      return NewInductionInfo(
+          kLinear,
+          kNop,
+          NewInductionInfo(kInvariant, kMul, a, b->op_a, nullptr),
+          NewInductionInfo(kInvariant, kMul, a, b->op_b, nullptr),
+          nullptr);
+    }
+  }
+  return nullptr;
+}
+
+HInductionVarAnalysis::InductionInfo* HInductionVarAnalysis::TransferNeg(InductionInfo* a) {
+  // Transfer over a unary negation: invariant or linear input
+  // yields a similar, but negated result.
+  if (a != nullptr) {
+    if (a->induction_class == kInvariant) {
+      return NewInductionInfo(kInvariant, kNeg, nullptr, a, nullptr);
+    } else if (a->induction_class == kLinear) {
+      return NewInductionInfo(
+          kLinear,
+          kNop,
+          NewInductionInfo(kInvariant, kNeg, nullptr, a->op_a, nullptr),
+          NewInductionInfo(kInvariant, kNeg, nullptr, a->op_b, nullptr),
+          nullptr);
+    }
+  }
+  return nullptr;
+}
+
+HInductionVarAnalysis::InductionInfo* HInductionVarAnalysis::TransferCycleOverPhi(HInstruction* phi) {
+  // Transfer within a cycle over a phi: only identical inputs
+  // can be combined into that input as result.
+  const size_t count = phi->InputCount();
+  CHECK_GT(count, 0u);
+  auto ita = cycle_.find(phi->InputAt(0)->GetId());
+  if (ita != cycle_.end()) {
+    InductionInfo* a = ita->second;
+    for (size_t i = 1; i < count; i++) {
+      auto itb = cycle_.find(phi->InputAt(i)->GetId());
+      if (itb == cycle_.end() ||!HInductionVarAnalysis::InductionEqual(a, itb->second)) {
+        return nullptr;
+      }
+    }
+    return a;
+  }
+  return nullptr;
+}
+
+HInductionVarAnalysis::InductionInfo* HInductionVarAnalysis::TransferCycleOverAddSub(
+    HLoopInformation* loop,
+    HInstruction* x,
+    HInstruction* y,
+    InductionOp op,
+    bool first) {
+  // Transfer within a cycle over an addition or subtraction: adding or
+  // subtracting an invariant value adds to the stride of the induction,
+  // starting with the phi value denoted by the unusual nullptr value.
+  auto it = cycle_.find(x->GetId());
+  if (it != cycle_.end()) {
+    InductionInfo* a = it->second;
+    InductionInfo* b = LookupInfo(loop, y);
+    if (b != nullptr && b->induction_class == kInvariant) {
+      if (a == nullptr) {
+        if (op == kSub) {  // negation required
+          return NewInductionInfo(kInvariant, kNeg, nullptr, b, nullptr);
+        }
+        return b;
+      } else if (a->induction_class == kInvariant) {
+        return NewInductionInfo(kInvariant, op, a, b, nullptr);
+      }
+    }
+  }
+  // On failure, try alternatives.
+  if (op == kAdd) {
+    // Try the other way around for an addition.
+    if (first) {
+      return TransferCycleOverAddSub(loop, y, x, op, false);
+    }
+  }
+  return nullptr;
+}
+
+void HInductionVarAnalysis::PutInfo(int loop_id, int id, InductionInfo* info) {
+  auto it = induction_.find(loop_id);
+  if (it == induction_.end()) {
+    it = induction_.Put(
+        loop_id, ArenaSafeMap<int, InductionInfo*>(std::less<int>(), graph_->GetArena()->Adapter()));
+  }
+  it->second.Overwrite(id, info);
+}
+
+HInductionVarAnalysis::InductionInfo* HInductionVarAnalysis::GetInfo(int loop_id, int id) {
+  auto it = induction_.find(loop_id);
+  if (it != induction_.end()) {
+    auto loop_it = it->second.find(id);
+    if (loop_it != it->second.end()) {
+      return loop_it->second;
+    }
+  }
+  return nullptr;
+}
+
+void HInductionVarAnalysis::AssignInfo(HLoopInformation* loop,
+                                       HInstruction* instruction,
+                                       InductionInfo* info) {
+  const int loopId = loop->GetHeader()->GetBlockId();
+  const int id = instruction->GetId();
+  PutInfo(loopId, id, info);
+}
+
+HInductionVarAnalysis::InductionInfo*
+HInductionVarAnalysis::LookupInfo(HLoopInformation* loop,
+                                  HInstruction* instruction) {
+  const int loop_id = loop->GetHeader()->GetBlockId();
+  const int id = instruction->GetId();
+  InductionInfo* info = GetInfo(loop_id, id);
+  if (info == nullptr && IsLoopInvariant(loop, instruction)) {
+    info = NewInductionInfo(kInvariant, kFetch, nullptr, nullptr, instruction);
+    PutInfo(loop_id, id, info);
+  }
+  return info;
+}
+
+bool HInductionVarAnalysis::InductionEqual(InductionInfo* info1,
+                                           InductionInfo* info2) {
+  // Test structural equality only, without accounting for simplifications.
+  if (info1 != nullptr && info2 != nullptr) {
+    return
+        info1->induction_class == info2->induction_class &&
+        info1->operation       == info2->operation       &&
+        info1->fetch           == info2->fetch           &&
+        InductionEqual(info1->op_a, info2->op_a)         &&
+        InductionEqual(info1->op_b, info2->op_b);
+  }
+  // Otherwise only two nullptrs are considered equal.
+  return info1 == info2;
+}
+
+std::string HInductionVarAnalysis::InductionToString(InductionInfo* info) {
+  if (info != nullptr) {
+    if (info->induction_class == kInvariant) {
+      std::string inv = "(";
+      inv += InductionToString(info->op_a);
+      switch (info->operation) {
+        case kNop: inv += " ? "; break;
+        case kAdd: inv += " + "; break;
+        case kSub:
+        case kNeg: inv += " - "; break;
+        case kMul: inv += " * "; break;
+        case kDiv: inv += " / "; break;
+        case kFetch:
+          CHECK(info->fetch != nullptr);
+          inv += std::to_string(info->fetch->GetId()) + ":" + info->fetch->DebugName();
+          break;
+      }
+      inv += InductionToString(info->op_b);
+      return inv + ")";
+    } else {
+      CHECK(info->operation == kNop);
+      if (info->induction_class == kLinear) {
+        return "(" + InductionToString(info->op_a) + " * i + " +
+                     InductionToString(info->op_b) + ")";
+      } else if (info->induction_class == kWrapAround) {
+        return "wrap(" + InductionToString(info->op_a) + ", " +
+                         InductionToString(info->op_b) + ")";
+      } else if (info->induction_class == kPeriodic) {
+        return "periodic(" + InductionToString(info->op_a) + ", " +
+                             InductionToString(info->op_b) + ")";
+      }
+    }
+  }
+  return "";
+}
+
+}  // namespace art
diff --git a/compiler/optimizing/induction_var_analysis.h b/compiler/optimizing/induction_var_analysis.h
new file mode 100644
index 0000000..09a0a38
--- /dev/null
+++ b/compiler/optimizing/induction_var_analysis.h
@@ -0,0 +1,171 @@
+/*
+ * 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_INDUCTION_VAR_ANALYSIS_H_
+#define ART_COMPILER_OPTIMIZING_INDUCTION_VAR_ANALYSIS_H_
+
+#include <string>
+
+#include "nodes.h"
+#include "optimization.h"
+
+namespace art {
+
+/**
+ * Induction variable analysis.
+ *
+ * Based on the paper by M. Gerlek et al.
+ * "Beyond Induction Variables: Detecting and Classifying Sequences Using a Demand-Driven SSA Form"
+ * (ACM Transactions on Programming Languages and Systems, Volume 17 Issue 1, Jan. 1995).
+ */
+class HInductionVarAnalysis : public HOptimization {
+ public:
+  explicit HInductionVarAnalysis(HGraph* graph);
+
+  // TODO: design public API useful in later phases
+
+  /**
+   * Returns string representation of induction found for the instruction
+   * in the given loop (for testing and debugging only).
+   */
+  std::string InductionToString(HLoopInformation* loop, HInstruction* instruction) {
+    return InductionToString(LookupInfo(loop, instruction));
+  }
+
+  void Run() OVERRIDE;
+
+ private:
+  static constexpr const char* kInductionPassName = "induction_var_analysis";
+
+  struct NodeInfo {
+    explicit NodeInfo(uint32_t d) : depth(d), done(false) {}
+    uint32_t depth;
+    bool done;
+  };
+
+  enum InductionClass {
+    kNone,
+    kInvariant,
+    kLinear,
+    kWrapAround,
+    kPeriodic,
+    kMonotonic
+  };
+
+  enum InductionOp {
+    kNop,  // no-operation: a true induction
+    kAdd,
+    kSub,
+    kNeg,
+    kMul,
+    kDiv,
+    kFetch
+  };
+
+  /**
+   * Defines a detected induction as:
+   *   (1) invariant:
+   *         operation: a + b, a - b, -b, a * b, a / b
+   *       or
+   *         fetch: fetch from HIR
+   *   (2) linear:
+   *         nop: a * i + b
+   *   (3) wrap-around
+   *         nop: a, then defined by b
+   *   (4) periodic
+   *         nop: a, then defined by b (repeated when exhausted)
+   *   (5) monotonic
+   *         // TODO: determine representation
+   */
+  struct InductionInfo : public ArenaObject<kArenaAllocMisc> {
+    InductionInfo(InductionClass ic,
+                  InductionOp op,
+                  InductionInfo* a,
+                  InductionInfo* b,
+                  HInstruction* f)
+        : induction_class(ic),
+          operation(op),
+          op_a(a),
+          op_b(b),
+          fetch(f) {}
+    InductionClass induction_class;
+    InductionOp operation;
+    InductionInfo* op_a;
+    InductionInfo* op_b;
+    HInstruction* fetch;
+  };
+
+  inline bool IsVisitedNode(int id) const {
+    return map_.find(id) != map_.end();
+  }
+
+  inline InductionInfo* NewInductionInfo(
+      InductionClass c,
+      InductionOp op,
+      InductionInfo* a,
+      InductionInfo* b,
+      HInstruction* i) {
+    return new (graph_->GetArena()) InductionInfo(c, op, a, b, i);
+  }
+
+  // Methods for analysis.
+  void VisitLoop(HLoopInformation* loop);
+  void VisitNode(HLoopInformation* loop, HInstruction* instruction);
+  uint32_t VisitDescendant(HLoopInformation* loop, HInstruction* instruction);
+  void ClassifyTrivial(HLoopInformation* loop, HInstruction* instruction);
+  void ClassifyNonTrivial(HLoopInformation* loop);
+
+  // Transfer operations.
+  InductionInfo* TransferPhi(InductionInfo* a, InductionInfo* b);
+  InductionInfo* TransferAddSub(InductionInfo* a, InductionInfo* b, InductionOp op);
+  InductionInfo* TransferMul(InductionInfo* a, InductionInfo* b);
+  InductionInfo* TransferNeg(InductionInfo* a);
+  InductionInfo* TransferCycleOverPhi(HInstruction* phi);
+  InductionInfo* TransferCycleOverAddSub(HLoopInformation* loop,
+                                         HInstruction* x,
+                                         HInstruction* y,
+                                         InductionOp op,
+                                         bool first);
+
+  // Assign and lookup.
+  void PutInfo(int loop_id, int id, InductionInfo* info);
+  InductionInfo* GetInfo(int loop_id, int id);
+  void AssignInfo(HLoopInformation* loop, HInstruction* instruction, InductionInfo* info);
+  InductionInfo* LookupInfo(HLoopInformation* loop, HInstruction* instruction);
+  bool InductionEqual(InductionInfo* info1, InductionInfo* info2);
+  std::string InductionToString(InductionInfo* info);
+
+  // Bookkeeping during and after analysis.
+  // TODO: fine tune data structures, only keep relevant data
+
+  uint32_t global_depth_;
+
+  ArenaVector<HInstruction*> stack_;
+  ArenaVector<HInstruction*> scc_;
+
+  // Mappings of instruction id to node and induction information.
+  ArenaSafeMap<int, NodeInfo> map_;
+  ArenaSafeMap<int, InductionInfo*> cycle_;
+
+  // Mapping from loop id to mapping of instruction id to induction information.
+  ArenaSafeMap<int, ArenaSafeMap<int, InductionInfo*>> induction_;
+
+  DISALLOW_COPY_AND_ASSIGN(HInductionVarAnalysis);
+};
+
+}  // namespace art
+
+#endif  // ART_COMPILER_OPTIMIZING_INDUCTION_VAR_ANALYSIS_H_
diff --git a/compiler/optimizing/induction_var_analysis_test.cc b/compiler/optimizing/induction_var_analysis_test.cc
new file mode 100644
index 0000000..2093e33
--- /dev/null
+++ b/compiler/optimizing/induction_var_analysis_test.cc
@@ -0,0 +1,514 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <regex>
+
+#include "base/arena_allocator.h"
+#include "builder.h"
+#include "gtest/gtest.h"
+#include "induction_var_analysis.h"
+#include "nodes.h"
+#include "optimizing_unit_test.h"
+
+namespace art {
+
+/**
+ * Fixture class for the InductionVarAnalysis tests.
+ */
+class InductionVarAnalysisTest : public testing::Test {
+ public:
+  InductionVarAnalysisTest() : pool_(), allocator_(&pool_) {
+    graph_ = CreateGraph(&allocator_);
+  }
+
+  ~InductionVarAnalysisTest() { }
+
+  // Builds single for-loop at depth d.
+  void BuildForLoop(int d, int n) {
+    ASSERT_LT(d, n);
+    loop_preheader_[d] = new (&allocator_) HBasicBlock(graph_);
+    graph_->AddBlock(loop_preheader_[d]);
+    loop_header_[d] = new (&allocator_) HBasicBlock(graph_);
+    graph_->AddBlock(loop_header_[d]);
+    loop_preheader_[d]->AddSuccessor(loop_header_[d]);
+    if (d < (n - 1)) {
+      BuildForLoop(d + 1, n);
+    }
+    loop_body_[d] = new (&allocator_) HBasicBlock(graph_);
+    graph_->AddBlock(loop_body_[d]);
+    loop_body_[d]->AddSuccessor(loop_header_[d]);
+    if (d < (n - 1)) {
+      loop_header_[d]->AddSuccessor(loop_preheader_[d + 1]);
+      loop_header_[d + 1]->AddSuccessor(loop_body_[d]);
+    } else {
+      loop_header_[d]->AddSuccessor(loop_body_[d]);
+    }
+  }
+
+  // Builds a n-nested loop in CFG where each loop at depth 0 <= d < n
+  // is defined as "for (int i_d = 0; i_d < 100; i_d++)". Tests can further
+  // populate the loop with instructions to set up interesting scenarios.
+  void BuildLoopNest(int n) {
+    ASSERT_LE(n, 10);
+    graph_->SetNumberOfVRegs(n + 2);
+
+    // Build basic blocks with entry, nested loop, exit.
+    entry_ = new (&allocator_) HBasicBlock(graph_);
+    graph_->AddBlock(entry_);
+    BuildForLoop(0, n);
+    exit_ = new (&allocator_) HBasicBlock(graph_);
+    graph_->AddBlock(exit_);
+    entry_->AddSuccessor(loop_preheader_[0]);
+    loop_header_[0]->AddSuccessor(exit_);
+    graph_->SetEntryBlock(entry_);
+    graph_->SetExitBlock(exit_);
+
+    // Provide entry and exit instructions.
+    // 0 : parameter
+    // 1 : constant 0
+    // 2 : constant 1
+    // 3 : constant 100
+    parameter_ = new (&allocator_)
+        HParameterValue(0, Primitive::kPrimNot, true);
+    entry_->AddInstruction(parameter_);
+    constant0_ = new (&allocator_) HConstant(Primitive::kPrimInt);
+    entry_->AddInstruction(constant0_);
+    constant1_ = new (&allocator_) HConstant(Primitive::kPrimInt);
+    entry_->AddInstruction(constant1_);
+    constant100_ = new (&allocator_) HConstant(Primitive::kPrimInt);
+    entry_->AddInstruction(constant100_);
+    exit_->AddInstruction(new (&allocator_) HExit());
+    induc_ = new (&allocator_) HLocal(n);
+    entry_->AddInstruction(induc_);
+    entry_->AddInstruction(new (&allocator_) HStoreLocal(induc_, constant0_));
+    tmp_ = new (&allocator_) HLocal(n + 1);
+    entry_->AddInstruction(tmp_);
+    entry_->AddInstruction(new (&allocator_) HStoreLocal(tmp_, constant100_));
+
+    // Provide loop instructions.
+    for (int d = 0; d < n; d++) {
+      basic_[d] = new (&allocator_) HLocal(d);
+      entry_->AddInstruction(basic_[d]);
+      loop_preheader_[d]->AddInstruction(
+           new (&allocator_) HStoreLocal(basic_[d], constant0_));
+      HInstruction* load = new (&allocator_)
+          HLoadLocal(basic_[d], Primitive::kPrimInt);
+      loop_header_[d]->AddInstruction(load);
+      HInstruction* compare = new (&allocator_)
+          HGreaterThanOrEqual(load, constant100_);
+      loop_header_[d]->AddInstruction(compare);
+      loop_header_[d]->AddInstruction(new (&allocator_) HIf(compare));
+      load = new (&allocator_) HLoadLocal(basic_[d], Primitive::kPrimInt);
+      loop_body_[d]->AddInstruction(load);
+      increment_[d] = new (&allocator_)
+          HAdd(Primitive::kPrimInt, load, constant1_);
+      loop_body_[d]->AddInstruction(increment_[d]);
+      loop_body_[d]->AddInstruction(
+               new (&allocator_) HStoreLocal(basic_[d], increment_[d]));
+      loop_body_[d]->AddInstruction(new (&allocator_) HGoto());
+    }
+  }
+
+  // Builds if-statement at depth d.
+  void BuildIf(int d, HBasicBlock** ifT, HBasicBlock **ifF) {
+    HBasicBlock* cond = new (&allocator_) HBasicBlock(graph_);
+    HBasicBlock* ifTrue = new (&allocator_) HBasicBlock(graph_);
+    HBasicBlock* ifFalse = new (&allocator_) HBasicBlock(graph_);
+    graph_->AddBlock(cond);
+    graph_->AddBlock(ifTrue);
+    graph_->AddBlock(ifFalse);
+    // Conditional split.
+    loop_header_[d]->ReplaceSuccessor(loop_body_[d], cond);
+    cond->AddSuccessor(ifTrue);
+    cond->AddSuccessor(ifFalse);
+    ifTrue->AddSuccessor(loop_body_[d]);
+    ifFalse->AddSuccessor(loop_body_[d]);
+    cond->AddInstruction(new (&allocator_) HIf(parameter_));
+    *ifT = ifTrue;
+    *ifF = ifFalse;
+  }
+
+  // Inserts instruction right before increment at depth d.
+  HInstruction* InsertInstruction(HInstruction* instruction, int d) {
+    loop_body_[d]->InsertInstructionBefore(instruction, increment_[d]);
+    return instruction;
+  }
+
+  // Inserts local load at depth d.
+  HInstruction* InsertLocalLoad(HLocal* local, int d) {
+    return InsertInstruction(
+        new (&allocator_) HLoadLocal(local, Primitive::kPrimInt), d);
+  }
+
+  // Inserts local store at depth d.
+  HInstruction* InsertLocalStore(HLocal* local, HInstruction* rhs, int d) {
+    return InsertInstruction(new (&allocator_) HStoreLocal(local, rhs), d);
+  }
+
+  // Inserts an array store with given local as subscript at depth d to
+  // enable tests to inspect the computed induction at that point easily.
+  HInstruction* InsertArrayStore(HLocal* subscript, int d) {
+    HInstruction* load = InsertInstruction(
+        new (&allocator_) HLoadLocal(subscript, Primitive::kPrimInt), d);
+    return InsertInstruction(new (&allocator_) HArraySet(
+        parameter_, load, constant0_, Primitive::kPrimInt, 0), d);
+  }
+
+  // Returns loop information of loop at depth d.
+  HLoopInformation* GetLoopInfo(int d) {
+    return loop_body_[d]->GetLoopInformation();
+  }
+
+  // Performs InductionVarAnalysis (after proper set up).
+  void PerformInductionVarAnalysis() {
+    ASSERT_TRUE(graph_->TryBuildingSsa());
+    iva_ = new (&allocator_) HInductionVarAnalysis(graph_);
+    iva_->Run();
+  }
+
+  // General building fields.
+  ArenaPool pool_;
+  ArenaAllocator allocator_;
+  HGraph* graph_;
+  HInductionVarAnalysis* iva_;
+
+  // Fixed basic blocks and instructions.
+  HBasicBlock* entry_;
+  HBasicBlock* exit_;
+  HInstruction* parameter_;  // "this"
+  HInstruction* constant0_;
+  HInstruction* constant1_;
+  HInstruction* constant100_;
+  HLocal* induc_;  // "vreg_n", the "k"
+  HLocal* tmp_;    // "vreg_n+1"
+
+  // Loop specifics.
+  HBasicBlock* loop_preheader_[10];
+  HBasicBlock* loop_header_[10];
+  HBasicBlock* loop_body_[10];
+  HInstruction* increment_[10];
+  HLocal* basic_[10];  // "vreg_d", the "i_d"
+};
+
+//
+// The actual InductionVarAnalysis tests.
+//
+
+TEST_F(InductionVarAnalysisTest, ProperLoopSetup) {
+  // Setup:
+  // for (int i_0 = 0; i_0 < 100; i_0++) {
+  //   ..
+  //     for (int i_9 = 0; i_9 < 100; i_9++) {
+  //     }
+  //   ..
+  // }
+  BuildLoopNest(10);
+  ASSERT_TRUE(graph_->TryBuildingSsa());
+  ASSERT_EQ(entry_->GetLoopInformation(), nullptr);
+  for (int d = 0; d < 1; d++) {
+    ASSERT_EQ(loop_preheader_[d]->GetLoopInformation(),
+              (d == 0) ? nullptr
+                       : loop_header_[d - 1]->GetLoopInformation());
+    ASSERT_NE(loop_header_[d]->GetLoopInformation(), nullptr);
+    ASSERT_NE(loop_body_[d]->GetLoopInformation(), nullptr);
+    ASSERT_EQ(loop_header_[d]->GetLoopInformation(),
+              loop_body_[d]->GetLoopInformation());
+  }
+  ASSERT_EQ(exit_->GetLoopInformation(), nullptr);
+}
+
+TEST_F(InductionVarAnalysisTest, FindBasicInductionVar) {
+  // Setup:
+  // for (int i = 0; i < 100; i++) {
+  //    a[i] = 0;
+  // }
+  BuildLoopNest(1);
+  HInstruction* store = InsertArrayStore(basic_[0], 0);
+  PerformInductionVarAnalysis();
+
+  EXPECT_STREQ(
+      "((2:Constant) * i + (1:Constant))",
+      iva_->InductionToString(GetLoopInfo(0), store->InputAt(1)).c_str());
+  EXPECT_STREQ(
+      "((2:Constant) * i + ((1:Constant) + (2:Constant)))",
+      iva_->InductionToString(GetLoopInfo(0), increment_[0]).c_str());
+}
+
+TEST_F(InductionVarAnalysisTest, FindDerivedInductionVarAdd) {
+  // Setup:
+  // for (int i = 0; i < 100; i++) {
+  //    k = 100 + i;
+  //    a[k] = 0;
+  // }
+  BuildLoopNest(1);
+  HInstruction *add = InsertInstruction(
+      new (&allocator_) HAdd(
+          Primitive::kPrimInt, constant100_, InsertLocalLoad(basic_[0], 0)), 0);
+  InsertLocalStore(induc_, add, 0);
+  HInstruction* store = InsertArrayStore(induc_, 0);
+  PerformInductionVarAnalysis();
+
+  EXPECT_STREQ(
+      "((2:Constant) * i + ((3:Constant) + (1:Constant)))",
+      iva_->InductionToString(GetLoopInfo(0), store->InputAt(1)).c_str());
+}
+
+TEST_F(InductionVarAnalysisTest, FindDerivedInductionVarSub) {
+  // Setup:
+  // for (int i = 0; i < 100; i++) {
+  //    k = 100 - i;
+  //    a[k] = 0;
+  // }
+  BuildLoopNest(1);
+  HInstruction *sub = InsertInstruction(
+      new (&allocator_) HSub(
+          Primitive::kPrimInt, constant100_, InsertLocalLoad(basic_[0], 0)), 0);
+  InsertLocalStore(induc_, sub, 0);
+  HInstruction* store = InsertArrayStore(induc_, 0);
+  PerformInductionVarAnalysis();
+
+  EXPECT_STREQ(
+      "(( - (2:Constant)) * i + ((3:Constant) - (1:Constant)))",
+      iva_->InductionToString(GetLoopInfo(0), store->InputAt(1)).c_str());
+}
+
+TEST_F(InductionVarAnalysisTest, FindDerivedInductionVarMul) {
+  // Setup:
+  // for (int i = 0; i < 100; i++) {
+  //    k = 100 * i;
+  //    a[k] = 0;
+  // }
+  BuildLoopNest(1);
+  HInstruction *mul = InsertInstruction(
+      new (&allocator_) HMul(
+          Primitive::kPrimInt, constant100_, InsertLocalLoad(basic_[0], 0)), 0);
+  InsertLocalStore(induc_, mul, 0);
+  HInstruction* store = InsertArrayStore(induc_, 0);
+  PerformInductionVarAnalysis();
+
+  EXPECT_STREQ(
+      "(((3:Constant) * (2:Constant)) * i + ((3:Constant) * (1:Constant)))",
+      iva_->InductionToString(GetLoopInfo(0), store->InputAt(1)).c_str());
+}
+
+TEST_F(InductionVarAnalysisTest, FindDerivedInductionVarNeg) {
+  // Setup:
+  // for (int i = 0; i < 100; i++) {
+  //    k = - i;
+  //    a[k] = 0;
+  // }
+  BuildLoopNest(1);
+  HInstruction *neg = InsertInstruction(
+      new (&allocator_) HNeg(
+          Primitive::kPrimInt, InsertLocalLoad(basic_[0], 0)), 0);
+  InsertLocalStore(induc_, neg, 0);
+  HInstruction* store = InsertArrayStore(induc_, 0);
+  PerformInductionVarAnalysis();
+
+  EXPECT_STREQ(
+      "(( - (2:Constant)) * i + ( - (1:Constant)))",
+      iva_->InductionToString(GetLoopInfo(0), store->InputAt(1)).c_str());
+}
+
+TEST_F(InductionVarAnalysisTest, FindChainInduction) {
+  // Setup:
+  // k = 0;
+  // for (int i = 0; i < 100; i++) {
+  //    k = k + 100;
+  //    a[k] = 0;
+  //    k = k - 1;
+  //    a[k] = 0;
+  // }
+  BuildLoopNest(1);
+  HInstruction *add = InsertInstruction(
+      new (&allocator_) HAdd(
+          Primitive::kPrimInt, InsertLocalLoad(induc_, 0), constant100_), 0);
+  InsertLocalStore(induc_, add, 0);
+  HInstruction* store1 = InsertArrayStore(induc_, 0);
+  HInstruction *sub = InsertInstruction(
+      new (&allocator_) HSub(
+          Primitive::kPrimInt, InsertLocalLoad(induc_, 0), constant1_), 0);
+  InsertLocalStore(induc_, sub, 0);
+  HInstruction* store2 = InsertArrayStore(induc_, 0);
+  PerformInductionVarAnalysis();
+
+  EXPECT_STREQ(
+      "(((3:Constant) - (2:Constant)) * i + ((1:Constant) + (3:Constant)))",
+      iva_->InductionToString(GetLoopInfo(0), store1->InputAt(1)).c_str());
+  EXPECT_STREQ(
+      "(((3:Constant) - (2:Constant)) * i + "
+      "(((1:Constant) + (3:Constant)) - (2:Constant)))",
+      iva_->InductionToString(GetLoopInfo(0), store2->InputAt(1)).c_str());
+}
+
+TEST_F(InductionVarAnalysisTest, FindTwoWayBasicInduction) {
+  // Setup:
+  // k = 0;
+  // for (int i = 0; i < 100; i++) {
+  //    if () k = k + 1;
+  //    else  k = k + 1;
+  //    a[k] = 0;
+  // }
+  BuildLoopNest(1);
+  HBasicBlock* ifTrue;
+  HBasicBlock* ifFalse;
+  BuildIf(0, &ifTrue, &ifFalse);
+  // True-branch.
+  HInstruction* load1 = new (&allocator_)
+      HLoadLocal(induc_, Primitive::kPrimInt);
+  ifTrue->AddInstruction(load1);
+  HInstruction* inc1 = new (&allocator_)
+      HAdd(Primitive::kPrimInt, load1, constant1_);
+  ifTrue->AddInstruction(inc1);
+  ifTrue->AddInstruction(new (&allocator_) HStoreLocal(induc_, inc1));
+  // False-branch.
+  HInstruction* load2 = new (&allocator_)
+      HLoadLocal(induc_, Primitive::kPrimInt);
+  ifFalse->AddInstruction(load2);
+  HInstruction* inc2 = new (&allocator_)
+        HAdd(Primitive::kPrimInt, load2, constant1_);
+  ifFalse->AddInstruction(inc2);
+  ifFalse->AddInstruction(new (&allocator_) HStoreLocal(induc_, inc2));
+  // Merge over a phi.
+  HInstruction* store = InsertArrayStore(induc_, 0);
+  PerformInductionVarAnalysis();
+
+  EXPECT_STREQ(
+      "((2:Constant) * i + ((1:Constant) + (2:Constant)))",
+      iva_->InductionToString(GetLoopInfo(0), store->InputAt(1)).c_str());
+}
+
+TEST_F(InductionVarAnalysisTest, FindTwoWayDerivedInduction) {
+  // Setup:
+  // for (int i = 0; i < 100; i++) {
+  //    if () k = i + 1;
+  //    else  k = i + 1;
+  //    a[k] = 0;
+  // }
+  BuildLoopNest(1);
+  HBasicBlock* ifTrue;
+  HBasicBlock* ifFalse;
+  BuildIf(0, &ifTrue, &ifFalse);
+  // True-branch.
+  HInstruction* load1 = new (&allocator_)
+      HLoadLocal(basic_[0], Primitive::kPrimInt);
+  ifTrue->AddInstruction(load1);
+  HInstruction* inc1 = new (&allocator_)
+      HAdd(Primitive::kPrimInt, load1, constant1_);
+  ifTrue->AddInstruction(inc1);
+  ifTrue->AddInstruction(new (&allocator_) HStoreLocal(induc_, inc1));
+  // False-branch.
+  HInstruction* load2 = new (&allocator_)
+      HLoadLocal(basic_[0], Primitive::kPrimInt);
+  ifFalse->AddInstruction(load2);
+  HInstruction* inc2 = new (&allocator_)
+        HAdd(Primitive::kPrimInt, load2, constant1_);
+  ifFalse->AddInstruction(inc2);
+  ifFalse->AddInstruction(new (&allocator_) HStoreLocal(induc_, inc2));
+  // Merge over a phi.
+  HInstruction* store = InsertArrayStore(induc_, 0);
+  PerformInductionVarAnalysis();
+
+  EXPECT_STREQ(
+      "((2:Constant) * i + ((1:Constant) + (2:Constant)))",
+      iva_->InductionToString(GetLoopInfo(0), store->InputAt(1)).c_str());
+}
+
+TEST_F(InductionVarAnalysisTest, FindFirstOrderWrapAroundInduction) {
+  // Setup:
+  // k = 0;
+  // for (int i = 0; i < 100; i++) {
+  //    a[k] = 0;
+  //    k = 100 - i;
+  // }
+  BuildLoopNest(1);
+  HInstruction* store = InsertArrayStore(induc_, 0);
+  HInstruction *sub = InsertInstruction(
+      new (&allocator_) HSub(
+          Primitive::kPrimInt, constant100_, InsertLocalLoad(basic_[0], 0)), 0);
+  InsertLocalStore(induc_, sub, 0);
+  PerformInductionVarAnalysis();
+
+  EXPECT_STREQ(
+      "wrap((1:Constant), "
+      "(( - (2:Constant)) * i + ((3:Constant) - (1:Constant))))",
+      iva_->InductionToString(GetLoopInfo(0), store->InputAt(1)).c_str());
+}
+
+TEST_F(InductionVarAnalysisTest, FindSecondOrderWrapAroundInduction) {
+  // Setup:
+  // k = 0;
+  // t = 100;
+  // for (int i = 0; i < 100; i++) {
+  //    a[k] = 0;
+  //    k = t;
+  //    t = 100 - i;
+  // }
+  BuildLoopNest(1);
+  HInstruction* store = InsertArrayStore(induc_, 0);
+  InsertLocalStore(induc_, InsertLocalLoad(tmp_, 0), 0);
+  HInstruction *sub = InsertInstruction(
+       new (&allocator_) HSub(
+           Primitive::kPrimInt, constant100_, InsertLocalLoad(basic_[0], 0)), 0);
+  InsertLocalStore(tmp_, sub, 0);
+  PerformInductionVarAnalysis();
+
+  EXPECT_STREQ(
+      "wrap((1:Constant), wrap((3:Constant), "
+      "(( - (2:Constant)) * i + ((3:Constant) - (1:Constant)))))",
+      iva_->InductionToString(GetLoopInfo(0), store->InputAt(1)).c_str());
+}
+
+TEST_F(InductionVarAnalysisTest, FindDeepLoopInduction) {
+  // Setup:
+  // k = 0;
+  // for (int i_0 = 0; i_0 < 100; i_0++) {
+  //   ..
+  //     for (int i_9 = 0; i_9 < 100; i_9++) {
+  //       k = 1 + k;
+  //       a[k] = 0;
+  //     }
+  //   ..
+  // }
+  BuildLoopNest(10);
+  HInstruction *inc = InsertInstruction(
+      new (&allocator_) HAdd(
+          Primitive::kPrimInt, constant1_, InsertLocalLoad(induc_, 9)), 9);
+  InsertLocalStore(induc_, inc, 9);
+  HInstruction* store = InsertArrayStore(induc_, 9);
+  PerformInductionVarAnalysis();
+
+  // Match exact number of constants, but be less strict on phi number,
+  // since that depends on the SSA building phase.
+  std::regex r("\\(\\(2:Constant\\) \\* i \\+ "
+               "\\(\\(2:Constant\\) \\+ \\(\\d+:Phi\\)\\)\\)");
+
+  for (int d = 0; d < 10; d++) {
+    if (d == 9) {
+      EXPECT_TRUE(std::regex_match(
+          iva_->InductionToString(GetLoopInfo(d), store->InputAt(1)), r));
+    } else {
+      EXPECT_STREQ(
+          "",
+          iva_->InductionToString(GetLoopInfo(d), store->InputAt(1)).c_str());
+    }
+    EXPECT_STREQ(
+        "((2:Constant) * i + ((1:Constant) + (2:Constant)))",
+        iva_->InductionToString(GetLoopInfo(d), increment_[d]).c_str());
+  }
+}
+
+}  // namespace art
diff --git a/compiler/optimizing/inliner.cc b/compiler/optimizing/inliner.cc
index ff90f32..112d42e 100644
--- a/compiler/optimizing/inliner.cc
+++ b/compiler/optimizing/inliner.cc
@@ -182,10 +182,10 @@
   ArtMethod* resolved_method;
   if (invoke_instruction->IsInvokeStaticOrDirect()) {
     MethodReference ref = invoke_instruction->AsInvokeStaticOrDirect()->GetTargetMethod();
-    resolved_method = class_linker->FindDexCache(*ref.dex_file)->GetResolvedMethod(
+    resolved_method = class_linker->FindDexCache(soa.Self(), *ref.dex_file)->GetResolvedMethod(
         ref.dex_method_index, class_linker->GetImagePointerSize());
   } else {
-    resolved_method = class_linker->FindDexCache(caller_dex_file)->GetResolvedMethod(
+    resolved_method = class_linker->FindDexCache(soa.Self(), caller_dex_file)->GetResolvedMethod(
         method_index, class_linker->GetImagePointerSize());
   }
 
diff --git a/compiler/optimizing/instruction_simplifier.cc b/compiler/optimizing/instruction_simplifier.cc
index df6e550..0ac26de 100644
--- a/compiler/optimizing/instruction_simplifier.cc
+++ b/compiler/optimizing/instruction_simplifier.cc
@@ -132,6 +132,12 @@
   // with
   //    ADD tmp, a, b
   //    NEG dst, tmp
+  // Note that we cannot optimize `(-a) + (-b)` to `-(a + b)` for floating-point.
+  // When `a` is `-0.0` and `b` is `0.0`, the former expression yields `0.0`,
+  // while the later yields `-0.0`.
+  if (!Primitive::IsIntegralType(binop->GetType())) {
+    return false;
+  }
   binop->ReplaceInput(left_neg->GetInput(), 0);
   binop->ReplaceInput(right_neg->GetInput(), 1);
   left_neg->GetBlock()->RemoveInstruction(left_neg);
diff --git a/compiler/optimizing/optimizing_compiler.cc b/compiler/optimizing/optimizing_compiler.cc
index f6bbace..6f251e8 100644
--- a/compiler/optimizing/optimizing_compiler.cc
+++ b/compiler/optimizing/optimizing_compiler.cc
@@ -540,11 +540,14 @@
                                                      CompilerDriver* compiler_driver,
                                                      const DexCompilationUnit& dex_compilation_unit,
                                                      PassObserver* pass_observer) const {
-  StackHandleScopeCollection handles(Thread::Current());
+  ScopedObjectAccess soa(Thread::Current());
+  StackHandleScopeCollection handles(soa.Self());
+  soa.Self()->TransitionFromRunnableToSuspended(kNative);
   RunOptimizations(graph, compiler_driver, compilation_stats_.get(),
                    dex_compilation_unit, pass_observer, &handles);
 
   if (graph->HasTryCatch()) {
+    soa.Self()->TransitionFromSuspendedToRunnable();
     return nullptr;
   }
 
@@ -582,6 +585,8 @@
       ArrayRef<const uint8_t>(*codegen->GetAssembler()->cfi().data()),
       ArrayRef<const LinkerPatch>(linker_patches));
   pass_observer->DumpDisassembly();
+
+  soa.Self()->TransitionFromSuspendedToRunnable();
   return compiled_method;
 }
 
@@ -709,7 +714,8 @@
     ScopedObjectAccess soa(Thread::Current());
     StackHandleScope<4> hs(soa.Self());
     ClassLinker* class_linker = dex_compilation_unit.GetClassLinker();
-    Handle<mirror::DexCache> dex_cache(hs.NewHandle(class_linker->FindDexCache(dex_file)));
+    Handle<mirror::DexCache> dex_cache(hs.NewHandle(class_linker->FindDexCache(
+        soa.Self(), dex_file)));
     Handle<mirror::ClassLoader> loader(hs.NewHandle(
         soa.Decode<mirror::ClassLoader*>(class_loader)));
     ArtMethod* art_method = compiler_driver->ResolveMethod(
@@ -795,8 +801,8 @@
                                             const DexFile& dex_file) const {
   CompilerDriver* compiler_driver = GetCompilerDriver();
   CompiledMethod* method = nullptr;
-  if (compiler_driver->IsMethodVerifiedWithoutFailures(method_idx, class_def_idx, dex_file) &&
-      !compiler_driver->GetVerifiedMethod(&dex_file, method_idx)->HasRuntimeThrow()) {
+  DCHECK(!compiler_driver->GetVerifiedMethod(&dex_file, method_idx)->HasRuntimeThrow());
+  if (compiler_driver->IsMethodVerifiedWithoutFailures(method_idx, class_def_idx, dex_file)) {
      method = TryCompile(code_item, access_flags, invoke_type, class_def_idx,
                          method_idx, jclass_loader, dex_file);
   } else {
diff --git a/compiler/optimizing/reference_type_propagation.cc b/compiler/optimizing/reference_type_propagation.cc
index 97b9972..516638b 100644
--- a/compiler/optimizing/reference_type_propagation.cc
+++ b/compiler/optimizing/reference_type_propagation.cc
@@ -79,6 +79,8 @@
     : HOptimization(graph, name),
       handles_(handles),
       worklist_(graph->GetArena(), kDefaultWorklistSize) {
+  // Mutator lock is required for NewHandle, but annotalysis ignores constructors.
+  ScopedObjectAccess soa(Thread::Current());
   ClassLinker* linker = Runtime::Current()->GetClassLinker();
   object_class_handle_ = handles_->NewHandle(linker->GetClassRoot(ClassLinker::kJavaLangObject));
   string_class_handle_ = handles_->NewHandle(linker->GetClassRoot(ClassLinker::kJavaLangString));
@@ -87,7 +89,6 @@
       handles_->NewHandle(linker->GetClassRoot(ClassLinker::kJavaLangThrowable));
 
   if (kIsDebugBuild) {
-    ScopedObjectAccess soa(Thread::Current());
     DCHECK(ReferenceTypeInfo::IsValidHandle(object_class_handle_));
     DCHECK(ReferenceTypeInfo::IsValidHandle(class_class_handle_));
     DCHECK(ReferenceTypeInfo::IsValidHandle(string_class_handle_));
@@ -362,7 +363,8 @@
     if (kIsDebugBuild) {
       ScopedObjectAccess soa(Thread::Current());
       ClassLinker* cl = Runtime::Current()->GetClassLinker();
-      mirror::DexCache* dex_cache = cl->FindDexCache(instr->AsInvoke()->GetDexFile());
+      mirror::DexCache* dex_cache = cl->FindDexCache(
+          soa.Self(), instr->AsInvoke()->GetDexFile(), false);
       ArtMethod* method = dex_cache->GetResolvedMethod(
           instr->AsInvoke()->GetDexMethodIndex(), cl->GetImagePointerSize());
       DCHECK(method != nullptr);
@@ -393,7 +395,8 @@
   DCHECK_EQ(instr->GetType(), Primitive::kPrimNot);
 
   ScopedObjectAccess soa(Thread::Current());
-  mirror::DexCache* dex_cache = Runtime::Current()->GetClassLinker()->FindDexCache(dex_file);
+  mirror::DexCache* dex_cache = Runtime::Current()->GetClassLinker()->FindDexCache(
+      soa.Self(), dex_file, false);
   // Get type from dex cache assuming it was populated by the verifier.
   SetClassAsTypeInfo(instr, dex_cache->GetResolvedType(type_idx), is_exact);
 }
@@ -431,7 +434,7 @@
 
   ScopedObjectAccess soa(Thread::Current());
   ClassLinker* cl = Runtime::Current()->GetClassLinker();
-  mirror::DexCache* dex_cache = cl->FindDexCache(info.GetDexFile());
+  mirror::DexCache* dex_cache = cl->FindDexCache(soa.Self(), info.GetDexFile(), false);
   ArtField* field = cl->GetResolvedField(info.GetFieldIndex(), dex_cache);
   // TODO: There are certain cases where we can't resolve the field.
   // b/21914925 is open to keep track of a repro case for this issue.
@@ -450,7 +453,7 @@
 void RTPVisitor::VisitLoadClass(HLoadClass* instr) {
   ScopedObjectAccess soa(Thread::Current());
   mirror::DexCache* dex_cache =
-      Runtime::Current()->GetClassLinker()->FindDexCache(instr->GetDexFile());
+      Runtime::Current()->GetClassLinker()->FindDexCache(soa.Self(), instr->GetDexFile(), false);
   // Get type from dex cache assuming it was populated by the verifier.
   mirror::Class* resolved_class = dex_cache->GetResolvedType(instr->GetTypeIndex());
   // TODO: investigating why we are still getting unresolved classes: b/22821472.
@@ -633,7 +636,7 @@
 
   ScopedObjectAccess soa(Thread::Current());
   ClassLinker* cl = Runtime::Current()->GetClassLinker();
-  mirror::DexCache* dex_cache = cl->FindDexCache(instr->GetDexFile());
+  mirror::DexCache* dex_cache = cl->FindDexCache(soa.Self(), instr->GetDexFile());
   ArtMethod* method = dex_cache->GetResolvedMethod(
       instr->GetDexMethodIndex(), cl->GetImagePointerSize());
   mirror::Class* klass = (method == nullptr) ? nullptr : method->GetReturnType(false);
diff --git a/compiler/utils/arm/assembler_thumb2.cc b/compiler/utils/arm/assembler_thumb2.cc
index 619ef6e..90ed10c 100644
--- a/compiler/utils/arm/assembler_thumb2.cc
+++ b/compiler/utils/arm/assembler_thumb2.cc
@@ -3363,17 +3363,17 @@
   Register tmp_reg = kNoRegister;
   if (!Address::CanHoldStoreOffsetThumb(type, offset)) {
     CHECK_NE(base, IP);
-    if (reg != IP &&
-        (type != kStoreWordPair || reg + 1 != IP)) {
+    if ((reg != IP) &&
+        ((type != kStoreWordPair) || (reg + 1 != IP))) {
       tmp_reg = IP;
     } else {
       // Be careful not to use IP twice (for `reg` (or `reg` + 1 in
-      // the case of a word-pair store)) and to build the Address
-      // object used by the store instruction(s) below).  Instead,
-      // save R5 on the stack (or R6 if R5 is not available), use it
-      // as secondary temporary register, and restore it after the
-      // store instruction has been emitted.
-      tmp_reg = base != R5 ? R5 : R6;
+      // the case of a word-pair store) and `base`) to build the
+      // Address object used by the store instruction(s) below.
+      // Instead, save R5 on the stack (or R6 if R5 is already used by
+      // `base`), use it as secondary temporary register, and restore
+      // it after the store instruction has been emitted.
+      tmp_reg = (base != R5) ? R5 : R6;
       Push(tmp_reg);
       if (base == SP) {
         offset += kRegisterSize;
@@ -3402,8 +3402,8 @@
       LOG(FATAL) << "UNREACHABLE";
       UNREACHABLE();
   }
-  if (tmp_reg != kNoRegister && tmp_reg != IP) {
-    DCHECK(tmp_reg == R5 || tmp_reg == R6);
+  if ((tmp_reg != kNoRegister) && (tmp_reg != IP)) {
+    CHECK((tmp_reg == R5) || (tmp_reg == R6));
     Pop(tmp_reg);
   }
 }
diff --git a/compiler/utils/assembler.h b/compiler/utils/assembler.h
index 3097cd5..64d76b8 100644
--- a/compiler/utils/assembler.h
+++ b/compiler/utils/assembler.h
@@ -53,9 +53,11 @@
 }
 namespace x86 {
   class X86Assembler;
+  class NearLabel;
 }
 namespace x86_64 {
   class X86_64Assembler;
+  class NearLabel;
 }
 
 class ExternalLabel {
@@ -126,7 +128,9 @@
   friend class mips::MipsAssembler;
   friend class mips64::Mips64Assembler;
   friend class x86::X86Assembler;
+  friend class x86::NearLabel;
   friend class x86_64::X86_64Assembler;
+  friend class x86_64::NearLabel;
 
   DISALLOW_COPY_AND_ASSIGN(Label);
 };
diff --git a/compiler/utils/x86/assembler_x86.cc b/compiler/utils/x86/assembler_x86.cc
index 9b3d792..a03f857 100644
--- a/compiler/utils/x86/assembler_x86.cc
+++ b/compiler/utils/x86/assembler_x86.cc
@@ -1510,6 +1510,38 @@
 }
 
 
+void X86Assembler::j(Condition condition, NearLabel* label) {
+  AssemblerBuffer::EnsureCapacity ensured(&buffer_);
+  if (label->IsBound()) {
+    static const int kShortSize = 2;
+    int offset = label->Position() - buffer_.Size();
+    CHECK_LE(offset, 0);
+    CHECK(IsInt<8>(offset - kShortSize));
+    EmitUint8(0x70 + condition);
+    EmitUint8((offset - kShortSize) & 0xFF);
+  } else {
+    EmitUint8(0x70 + condition);
+    EmitLabelLink(label);
+  }
+}
+
+
+void X86Assembler::jecxz(NearLabel* label) {
+  AssemblerBuffer::EnsureCapacity ensured(&buffer_);
+  if (label->IsBound()) {
+    static const int kShortSize = 2;
+    int offset = label->Position() - buffer_.Size();
+    CHECK_LE(offset, 0);
+    CHECK(IsInt<8>(offset - kShortSize));
+    EmitUint8(0xE3);
+    EmitUint8((offset - kShortSize) & 0xFF);
+  } else {
+    EmitUint8(0xE3);
+    EmitLabelLink(label);
+  }
+}
+
+
 void X86Assembler::jmp(Register reg) {
   AssemblerBuffer::EnsureCapacity ensured(&buffer_);
   EmitUint8(0xFF);
@@ -1543,6 +1575,22 @@
 }
 
 
+void X86Assembler::jmp(NearLabel* label) {
+  AssemblerBuffer::EnsureCapacity ensured(&buffer_);
+  if (label->IsBound()) {
+    static const int kShortSize = 2;
+    int offset = label->Position() - buffer_.Size();
+    CHECK_LE(offset, 0);
+    CHECK(IsInt<8>(offset - kShortSize));
+    EmitUint8(0xEB);
+    EmitUint8((offset - kShortSize) & 0xFF);
+  } else {
+    EmitUint8(0xEB);
+    EmitLabelLink(label);
+  }
+}
+
+
 void X86Assembler::repne_scasw() {
   AssemblerBuffer::EnsureCapacity ensured(&buffer_);
   EmitUint8(0x66);
@@ -1675,6 +1723,21 @@
 }
 
 
+void X86Assembler::Bind(NearLabel* label) {
+  int bound = buffer_.Size();
+  CHECK(!label->IsBound());  // Labels can only be bound once.
+  while (label->IsLinked()) {
+    int position = label->LinkPosition();
+    uint8_t delta = buffer_.Load<uint8_t>(position);
+    int offset = bound - (position + 1);
+    CHECK(IsInt<8>(offset));
+    buffer_.Store<int8_t>(position, offset);
+    label->position_ = delta != 0u ? label->position_ - delta : 0;
+  }
+  label->BindTo(bound);
+}
+
+
 void X86Assembler::EmitOperand(int reg_or_opcode, const Operand& operand) {
   CHECK_GE(reg_or_opcode, 0);
   CHECK_LT(reg_or_opcode, 8);
@@ -1736,6 +1799,21 @@
 }
 
 
+void X86Assembler::EmitLabelLink(NearLabel* label) {
+  CHECK(!label->IsBound());
+  int position = buffer_.Size();
+  if (label->IsLinked()) {
+    // Save the delta in the byte that we have to play with.
+    uint32_t delta = position - label->LinkPosition();
+    CHECK(IsUint<8>(delta));
+    EmitUint8(delta & 0xFF);
+  } else {
+    EmitUint8(0);
+  }
+  label->LinkTo(position);
+}
+
+
 void X86Assembler::EmitGenericShift(int reg_or_opcode,
                                     const Operand& operand,
                                     const Immediate& imm) {
diff --git a/compiler/utils/x86/assembler_x86.h b/compiler/utils/x86/assembler_x86.h
index a9227f3..0c90f28 100644
--- a/compiler/utils/x86/assembler_x86.h
+++ b/compiler/utils/x86/assembler_x86.h
@@ -203,6 +203,30 @@
 };
 
 
+// This is equivalent to the Label class, used in a slightly different context. We
+// inherit the functionality of the Label class, but prevent unintended
+// derived-to-base conversions by making the base class private.
+class NearLabel : private Label {
+ public:
+  NearLabel() : Label() {}
+
+  // Expose the Label routines that we need.
+  using Label::Position;
+  using Label::LinkPosition;
+  using Label::IsBound;
+  using Label::IsUnused;
+  using Label::IsLinked;
+
+ private:
+  using Label::BindTo;
+  using Label::LinkTo;
+
+  friend class x86::X86Assembler;
+
+  DISALLOW_COPY_AND_ASSIGN(NearLabel);
+};
+
+
 class X86Assembler FINAL : public Assembler {
  public:
   X86Assembler() {}
@@ -464,10 +488,13 @@
   void hlt();
 
   void j(Condition condition, Label* label);
+  void j(Condition condition, NearLabel* label);
+  void jecxz(NearLabel* label);
 
   void jmp(Register reg);
   void jmp(const Address& address);
   void jmp(Label* label);
+  void jmp(NearLabel* label);
 
   void repne_scasw();
   void repe_cmpsw();
@@ -506,6 +533,7 @@
   int PreferredLoopAlignment() { return 16; }
   void Align(int alignment, int offset);
   void Bind(Label* label);
+  void Bind(NearLabel* label);
 
   //
   // Overridden common assembler high-level functionality
@@ -652,6 +680,7 @@
   void EmitComplex(int rm, const Operand& operand, const Immediate& immediate);
   void EmitLabel(Label* label, int instruction_size);
   void EmitLabelLink(Label* label);
+  void EmitLabelLink(NearLabel* label);
 
   void EmitGenericShift(int rm, const Operand& operand, const Immediate& imm);
   void EmitGenericShift(int rm, const Operand& operand, Register shifter);
diff --git a/compiler/utils/x86/assembler_x86_test.cc b/compiler/utils/x86/assembler_x86_test.cc
index 731b5f4..9ac54af 100644
--- a/compiler/utils/x86/assembler_x86_test.cc
+++ b/compiler/utils/x86/assembler_x86_test.cc
@@ -243,4 +243,43 @@
   DriverStr(expected, "bsrl_address");
 }
 
+/////////////////
+// Near labels //
+/////////////////
+
+TEST_F(AssemblerX86Test, Jecxz) {
+  x86::NearLabel target;
+  GetAssembler()->jecxz(&target);
+  GetAssembler()->addl(x86::EDI, x86::Address(x86::ESP, 4));
+  GetAssembler()->Bind(&target);
+  const char* expected =
+    "jecxz 1f\n"
+    "addl 4(%ESP),%EDI\n"
+    "1:\n";
+
+  DriverStr(expected, "jecxz");
+}
+
+TEST_F(AssemblerX86Test, NearLabel) {
+  // Test both forward and backward branches.
+  x86::NearLabel start, target;
+  GetAssembler()->Bind(&start);
+  GetAssembler()->j(x86::kEqual, &target);
+  GetAssembler()->jmp(&target);
+  GetAssembler()->jecxz(&target);
+  GetAssembler()->addl(x86::EDI, x86::Address(x86::ESP, 4));
+  GetAssembler()->Bind(&target);
+  GetAssembler()->j(x86::kNotEqual, &start);
+  GetAssembler()->jmp(&start);
+  const char* expected =
+    "1: je 2f\n"
+    "jmp 2f\n"
+    "jecxz 2f\n"
+    "addl 4(%ESP),%EDI\n"
+    "2: jne 1b\n"
+    "jmp 1b\n";
+
+  DriverStr(expected, "near_label");
+}
+
 }  // namespace art
diff --git a/compiler/utils/x86_64/assembler_x86_64.cc b/compiler/utils/x86_64/assembler_x86_64.cc
index dc61c99..88ea990 100644
--- a/compiler/utils/x86_64/assembler_x86_64.cc
+++ b/compiler/utils/x86_64/assembler_x86_64.cc
@@ -1971,6 +1971,38 @@
 }
 
 
+void X86_64Assembler::j(Condition condition, NearLabel* label) {
+  AssemblerBuffer::EnsureCapacity ensured(&buffer_);
+  if (label->IsBound()) {
+    static const int kShortSize = 2;
+    int offset = label->Position() - buffer_.Size();
+    CHECK_LE(offset, 0);
+    CHECK(IsInt<8>(offset - kShortSize));
+    EmitUint8(0x70 + condition);
+    EmitUint8((offset - kShortSize) & 0xFF);
+  } else {
+    EmitUint8(0x70 + condition);
+    EmitLabelLink(label);
+  }
+}
+
+
+void X86_64Assembler::jrcxz(NearLabel* label) {
+  AssemblerBuffer::EnsureCapacity ensured(&buffer_);
+  if (label->IsBound()) {
+    static const int kShortSize = 2;
+    int offset = label->Position() - buffer_.Size();
+    CHECK_LE(offset, 0);
+    CHECK(IsInt<8>(offset - kShortSize));
+    EmitUint8(0xE3);
+    EmitUint8((offset - kShortSize) & 0xFF);
+  } else {
+    EmitUint8(0xE3);
+    EmitLabelLink(label);
+  }
+}
+
+
 void X86_64Assembler::jmp(CpuRegister reg) {
   AssemblerBuffer::EnsureCapacity ensured(&buffer_);
   EmitOptionalRex32(reg);
@@ -2006,6 +2038,22 @@
 }
 
 
+void X86_64Assembler::jmp(NearLabel* label) {
+  AssemblerBuffer::EnsureCapacity ensured(&buffer_);
+  if (label->IsBound()) {
+    static const int kShortSize = 2;
+    int offset = label->Position() - buffer_.Size();
+    CHECK_LE(offset, 0);
+    CHECK(IsInt<8>(offset - kShortSize));
+    EmitUint8(0xEB);
+    EmitUint8((offset - kShortSize) & 0xFF);
+  } else {
+    EmitUint8(0xEB);
+    EmitLabelLink(label);
+  }
+}
+
+
 void X86_64Assembler::rep_movsw() {
   AssemblerBuffer::EnsureCapacity ensured(&buffer_);
   EmitUint8(0x66);
@@ -2187,6 +2235,21 @@
 }
 
 
+void X86_64Assembler::Bind(NearLabel* label) {
+  int bound = buffer_.Size();
+  CHECK(!label->IsBound());  // Labels can only be bound once.
+  while (label->IsLinked()) {
+    int position = label->LinkPosition();
+    uint8_t delta = buffer_.Load<uint8_t>(position);
+    int offset = bound - (position + 1);
+    CHECK(IsInt<8>(offset));
+    buffer_.Store<int8_t>(position, offset);
+    label->position_ = delta != 0u ? label->position_ - delta : 0;
+  }
+  label->BindTo(bound);
+}
+
+
 void X86_64Assembler::EmitOperand(uint8_t reg_or_opcode, const Operand& operand) {
   CHECK_GE(reg_or_opcode, 0);
   CHECK_LT(reg_or_opcode, 8);
@@ -2256,6 +2319,21 @@
 }
 
 
+void X86_64Assembler::EmitLabelLink(NearLabel* label) {
+  CHECK(!label->IsBound());
+  int position = buffer_.Size();
+  if (label->IsLinked()) {
+    // Save the delta in the byte that we have to play with.
+    uint32_t delta = position - label->LinkPosition();
+    CHECK(IsUint<8>(delta));
+    EmitUint8(delta & 0xFF);
+  } else {
+    EmitUint8(0);
+  }
+  label->LinkTo(position);
+}
+
+
 void X86_64Assembler::EmitGenericShift(bool wide,
                                        int reg_or_opcode,
                                        CpuRegister reg,
diff --git a/compiler/utils/x86_64/assembler_x86_64.h b/compiler/utils/x86_64/assembler_x86_64.h
index da42213..c38aba5 100644
--- a/compiler/utils/x86_64/assembler_x86_64.h
+++ b/compiler/utils/x86_64/assembler_x86_64.h
@@ -302,6 +302,30 @@
 };
 
 
+// This is equivalent to the Label class, used in a slightly different context. We
+// inherit the functionality of the Label class, but prevent unintended
+// derived-to-base conversions by making the base class private.
+class NearLabel : private Label {
+ public:
+  NearLabel() : Label() {}
+
+  // Expose the Label routines that we need.
+  using Label::Position;
+  using Label::LinkPosition;
+  using Label::IsBound;
+  using Label::IsUnused;
+  using Label::IsLinked;
+
+ private:
+  using Label::BindTo;
+  using Label::LinkTo;
+
+  friend class x86_64::X86_64Assembler;
+
+  DISALLOW_COPY_AND_ASSIGN(NearLabel);
+};
+
+
 class X86_64Assembler FINAL : public Assembler {
  public:
   X86_64Assembler() {}
@@ -588,10 +612,13 @@
   void hlt();
 
   void j(Condition condition, Label* label);
+  void j(Condition condition, NearLabel* label);
+  void jrcxz(NearLabel* label);
 
   void jmp(CpuRegister reg);
   void jmp(const Address& address);
   void jmp(Label* label);
+  void jmp(NearLabel* label);
 
   X86_64Assembler* lock();
   void cmpxchgl(const Address& address, CpuRegister reg);
@@ -639,6 +666,7 @@
   int PreferredLoopAlignment() { return 16; }
   void Align(int alignment, int offset);
   void Bind(Label* label);
+  void Bind(NearLabel* label);
 
   //
   // Overridden common assembler high-level functionality
@@ -809,6 +837,7 @@
   void EmitComplex(uint8_t rm, const Operand& operand, const Immediate& immediate);
   void EmitLabel(Label* label, int instruction_size);
   void EmitLabelLink(Label* label);
+  void EmitLabelLink(NearLabel* label);
 
   void EmitGenericShift(bool wide, int rm, CpuRegister reg, const Immediate& imm);
   void EmitGenericShift(bool wide, int rm, CpuRegister operand, CpuRegister shifter);
diff --git a/compiler/utils/x86_64/assembler_x86_64_test.cc b/compiler/utils/x86_64/assembler_x86_64_test.cc
index 8673f03..9e64b47 100644
--- a/compiler/utils/x86_64/assembler_x86_64_test.cc
+++ b/compiler/utils/x86_64/assembler_x86_64_test.cc
@@ -1179,6 +1179,47 @@
   DriverStr(expected, "bsrq_address");
 }
 
+/////////////////
+// Near labels //
+/////////////////
+
+TEST_F(AssemblerX86_64Test, Jrcxz) {
+  x86_64::NearLabel target;
+  GetAssembler()->jrcxz(&target);
+  GetAssembler()->addl(x86_64::CpuRegister(x86_64::RDI),
+                       x86_64::Address(x86_64::CpuRegister(x86_64::RSP), 4));
+  GetAssembler()->Bind(&target);
+  const char* expected =
+    "jrcxz 1f\n"
+    "addl 4(%RSP),%EDI\n"
+    "1:\n";
+
+  DriverStr(expected, "jrcxz");
+}
+
+TEST_F(AssemblerX86_64Test, NearLabel) {
+  // Test both forward and backward branches.
+  x86_64::NearLabel start, target;
+  GetAssembler()->Bind(&start);
+  GetAssembler()->j(x86_64::kEqual, &target);
+  GetAssembler()->jmp(&target);
+  GetAssembler()->jrcxz(&target);
+  GetAssembler()->addl(x86_64::CpuRegister(x86_64::RDI),
+                       x86_64::Address(x86_64::CpuRegister(x86_64::RSP), 4));
+  GetAssembler()->Bind(&target);
+  GetAssembler()->j(x86_64::kNotEqual, &start);
+  GetAssembler()->jmp(&start);
+  const char* expected =
+    "1: je 2f\n"
+    "jmp 2f\n"
+    "jrcxz 2f\n"
+    "addl 4(%RSP),%EDI\n"
+    "2: jne 1b\n"
+    "jmp 1b\n";
+
+  DriverStr(expected, "near_label");
+}
+
 std::string setcc_test_fn(AssemblerX86_64Test::Base* assembler_test,
                           x86_64::X86_64Assembler* assembler) {
   // From Condition
diff --git a/dex2oat/dex2oat.cc b/dex2oat/dex2oat.cc
index 99736e9..07cf88c 100644
--- a/dex2oat/dex2oat.cc
+++ b/dex2oat/dex2oat.cc
@@ -1207,6 +1207,14 @@
     oat_file_.reset();
   }
 
+  void Shutdown() {
+    ScopedObjectAccess soa(Thread::Current());
+    for (jobject dex_cache : dex_caches_) {
+      soa.Env()->DeleteLocalRef(dex_cache);
+    }
+    dex_caches_.clear();
+  }
+
   // Set up the environment for compilation. Includes starting the runtime and loading/opening the
   // boot class path.
   bool Setup() {
@@ -1320,8 +1328,9 @@
       compiled_methods_.reset(nullptr);  // By default compile everything.
     }
 
+    ClassLinker* const class_linker = Runtime::Current()->GetClassLinker();
     if (boot_image_option_.empty()) {
-      dex_files_ = Runtime::Current()->GetClassLinker()->GetBootClassPath();
+      dex_files_ = class_linker->GetBootClassPath();
     } else {
       if (dex_filenames_.empty()) {
         ATRACE_BEGIN("Opening zip archive from file descriptor");
@@ -1374,11 +1383,15 @@
         }
       }
     }
-    // Ensure opened dex files are writable for dex-to-dex transformations.
+    // Ensure opened dex files are writable for dex-to-dex transformations. Also ensure that
+    // the dex caches stay live since we don't want class unloading to occur during compilation.
     for (const auto& dex_file : dex_files_) {
       if (!dex_file->EnableWrite()) {
         PLOG(ERROR) << "Failed to make .dex file writeable '" << dex_file->GetLocation() << "'\n";
       }
+      ScopedObjectAccess soa(self);
+      dex_caches_.push_back(soa.AddLocalReference<jobject>(
+          class_linker->RegisterDexFile(*dex_file)));
     }
 
     // If we use a swap file, ensure we are above the threshold to make it necessary.
@@ -1423,6 +1436,7 @@
     // Handle and ClassLoader creation needs to come after Runtime::Create
     jobject class_loader = nullptr;
     Thread* self = Thread::Current();
+
     if (!boot_image_option_.empty()) {
       ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
       OpenClassPathFiles(runtime_->GetClassPathString(), dex_files_, &class_path_files_);
@@ -1957,6 +1971,7 @@
   bool is_host_;
   std::string android_root_;
   std::vector<const DexFile*> dex_files_;
+  std::vector<jobject> dex_caches_;
   std::vector<std::unique_ptr<const DexFile>> opened_dex_files_;
   std::unique_ptr<CompilerDriver> driver_;
   std::vector<std::string> verbose_methods_;
@@ -2107,11 +2122,15 @@
     return EXIT_FAILURE;
   }
 
+  bool result;
   if (dex2oat.IsImage()) {
-    return CompileImage(dex2oat);
+    result = CompileImage(dex2oat);
   } else {
-    return CompileApp(dex2oat);
+    result = CompileApp(dex2oat);
   }
+
+  dex2oat.Shutdown();
+  return result;
 }
 }  // namespace art
 
diff --git a/dexlist/Android.mk b/dexlist/Android.mk
index 9fbd847..6ec6c97 100755
--- a/dexlist/Android.mk
+++ b/dexlist/Android.mk
@@ -14,8 +14,6 @@
 
 # TODO(ajcbik): Art-i-fy this makefile
 
-# TODO(ajcbik): rename dexlist2 into dexlist when Dalvik version is removed
-
 LOCAL_PATH:= $(call my-dir)
 
 dexlist_src_files := dexlist.cc
@@ -33,7 +31,7 @@
 LOCAL_C_INCLUDES := $(dexlist_c_includes)
 LOCAL_CFLAGS += -Wall
 LOCAL_SHARED_LIBRARIES += $(dexlist_libraries)
-LOCAL_MODULE := dexlist2
+LOCAL_MODULE := dexlist
 LOCAL_MODULE_TAGS := optional
 LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES)
 include $(BUILD_EXECUTABLE)
@@ -49,6 +47,6 @@
 LOCAL_C_INCLUDES := $(dexlist_c_includes)
 LOCAL_CFLAGS += -Wall
 LOCAL_SHARED_LIBRARIES += $(dexlist_libraries)
-LOCAL_MODULE := dexlist2
+LOCAL_MODULE := dexlist
 LOCAL_MULTILIB := $(ART_MULTILIB_OVERRIDE_host)
 include $(BUILD_HOST_EXECUTABLE)
diff --git a/dexlist/dexlist.cc b/dexlist/dexlist.cc
index d8fd242..1d0f75e 100644
--- a/dexlist/dexlist.cc
+++ b/dexlist/dexlist.cc
@@ -235,7 +235,7 @@
         gOptions.outputFileName = optarg;
         break;
       case 'm':
-        // If -m x.y.z is given, then find all instances of the
+        // If -m p.c.m is given, then find all instances of the
         // fully-qualified method name. This isn't really what
         // dexlist is for, but it's easy to do it here.
         {
diff --git a/dexlist/dexlist_test.cc b/dexlist/dexlist_test.cc
index 7b1b63d..82179dea 100644
--- a/dexlist/dexlist_test.cc
+++ b/dexlist/dexlist_test.cc
@@ -42,12 +42,11 @@
 
   // Runs test with given arguments.
   bool Exec(const std::vector<std::string>& args, std::string* error_msg) {
-    // TODO(ajcbik): dexlist2 -> dexlist
     std::string file_path = GetTestAndroidRoot();
     if (IsHost()) {
-      file_path += "/bin/dexlist2";
+      file_path += "/bin/dexlist";
     } else {
-      file_path += "/xbin/dexlist2";
+      file_path += "/xbin/dexlist";
     }
     EXPECT_TRUE(OS::FileExists(file_path.c_str())) << file_path << " should be a valid file path";
     std::vector<std::string> exec_argv = { file_path };
diff --git a/oatdump/oatdump.cc b/oatdump/oatdump.cc
index 44b78ff..c553a18 100644
--- a/oatdump/oatdump.cc
+++ b/oatdump/oatdump.cc
@@ -78,6 +78,21 @@
   "kClassRoots",
 };
 
+// Map is so that we don't allocate multiple dex files for the same OatDexFile.
+static std::map<const OatFile::OatDexFile*,
+                std::unique_ptr<const DexFile>> opened_dex_files;
+
+const DexFile* OpenDexFile(const OatFile::OatDexFile* oat_dex_file, std::string* error_msg) {
+  DCHECK(oat_dex_file != nullptr);
+  auto it = opened_dex_files.find(oat_dex_file);
+  if (it != opened_dex_files.end()) {
+    return it->second.get();
+  }
+  const DexFile* ret = oat_dex_file->OpenDexFile(error_msg).release();
+  opened_dex_files.emplace(oat_dex_file, std::unique_ptr<const DexFile>(ret));
+  return ret;
+}
+
 class OatSymbolizer FINAL {
  public:
   class RodataWriter FINAL : public CodeOutput {
@@ -159,8 +174,8 @@
 
   void WalkOatDexFile(const OatFile::OatDexFile* oat_dex_file, Callback callback) {
     std::string error_msg;
-    std::unique_ptr<const DexFile> dex_file(oat_dex_file->OpenDexFile(&error_msg));
-    if (dex_file.get() == nullptr) {
+    const DexFile* const dex_file = OpenDexFile(oat_dex_file, &error_msg);
+    if (dex_file == nullptr) {
       return;
     }
     for (size_t class_def_index = 0;
@@ -172,7 +187,7 @@
       switch (type) {
         case kOatClassAllCompiled:
         case kOatClassSomeCompiled:
-          WalkOatClass(oat_class, *dex_file.get(), class_def, callback);
+          WalkOatClass(oat_class, *dex_file, class_def, callback);
           break;
 
         case kOatClassNoneCompiled:
@@ -504,8 +519,8 @@
       const OatFile::OatDexFile* oat_dex_file = oat_dex_files_[i];
       CHECK(oat_dex_file != nullptr);
       std::string error_msg;
-      std::unique_ptr<const DexFile> dex_file(oat_dex_file->OpenDexFile(&error_msg));
-      if (dex_file.get() == nullptr) {
+      const DexFile* const dex_file = OpenDexFile(oat_dex_file, &error_msg);
+      if (dex_file == nullptr) {
         LOG(WARNING) << "Failed to open dex file '" << oat_dex_file->GetDexFileLocation()
             << "': " << error_msg;
       } else {
@@ -533,8 +548,8 @@
       const OatFile::OatDexFile* oat_dex_file = oat_dex_files_[i];
       CHECK(oat_dex_file != nullptr);
       std::string error_msg;
-      std::unique_ptr<const DexFile> dex_file(oat_dex_file->OpenDexFile(&error_msg));
-      if (dex_file.get() == nullptr) {
+      const DexFile* const dex_file = OpenDexFile(oat_dex_file, &error_msg);
+      if (dex_file == nullptr) {
         LOG(WARNING) << "Failed to open dex file '" << oat_dex_file->GetDexFileLocation()
             << "': " << error_msg;
         continue;
@@ -593,8 +608,8 @@
     // Create the verifier early.
 
     std::string error_msg;
-    std::unique_ptr<const DexFile> dex_file(oat_dex_file.OpenDexFile(&error_msg));
-    if (dex_file.get() == nullptr) {
+    const DexFile* const dex_file = OpenDexFile(&oat_dex_file, &error_msg);
+    if (dex_file == nullptr) {
       os << "NOT FOUND: " << error_msg << "\n\n";
       os << std::flush;
       return false;
@@ -621,7 +636,7 @@
          << " (" << oat_class.GetType() << ")\n";
       // TODO: include bitmap here if type is kOatClassSomeCompiled?
       if (options_.list_classes_) continue;
-      if (!DumpOatClass(&vios, oat_class, *(dex_file.get()), class_def, &stop_analysis)) {
+      if (!DumpOatClass(&vios, oat_class, *dex_file, class_def, &stop_analysis)) {
         success = false;
       }
       if (stop_analysis) {
@@ -638,7 +653,7 @@
     std::string error_msg;
     std::string dex_file_location = oat_dex_file.GetDexFileLocation();
 
-    std::unique_ptr<const DexFile> dex_file(oat_dex_file.OpenDexFile(&error_msg));
+    const DexFile* const dex_file = OpenDexFile(&oat_dex_file, &error_msg);
     if (dex_file == nullptr) {
       os << "Failed to open dex file '" << dex_file_location << "': " << error_msg;
       return false;
@@ -812,11 +827,15 @@
       DumpDexCode(vios->Stream(), dex_file, code_item);
     }
 
+    std::unique_ptr<StackHandleScope<1>> hs;
     std::unique_ptr<verifier::MethodVerifier> verifier;
     if (Runtime::Current() != nullptr) {
+      // We need to have the handle scope stay live until after the verifier since the verifier has
+      // a handle to the dex cache from hs.
+      hs.reset(new StackHandleScope<1>(Thread::Current()));
       vios->Stream() << "VERIFIER TYPE ANALYSIS:\n";
       ScopedIndentation indent2(vios);
-      verifier.reset(DumpVerifier(vios,
+      verifier.reset(DumpVerifier(vios, hs.get(),
                                   dex_method_idx, &dex_file, class_def, code_item,
                                   method_access_flags));
     }
@@ -1389,6 +1408,7 @@
   }
 
   verifier::MethodVerifier* DumpVerifier(VariableIndentationOutputStream* vios,
+                                         StackHandleScope<1>* hs,
                                          uint32_t dex_method_idx,
                                          const DexFile* dex_file,
                                          const DexFile::ClassDef& class_def,
@@ -1396,9 +1416,8 @@
                                          uint32_t method_access_flags) {
     if ((method_access_flags & kAccNative) == 0) {
       ScopedObjectAccess soa(Thread::Current());
-      StackHandleScope<1> hs(soa.Self());
       Handle<mirror::DexCache> dex_cache(
-          hs.NewHandle(Runtime::Current()->GetClassLinker()->FindDexCache(*dex_file)));
+          hs->NewHandle(Runtime::Current()->GetClassLinker()->RegisterDexFile(*dex_file)));
       DCHECK(options_.class_loader_ != nullptr);
       return verifier::MethodVerifier::VerifyMethodAndDump(
           soa.Self(), vios, dex_method_idx, dex_file, dex_cache, *options_.class_loader_,
@@ -1599,10 +1618,13 @@
       dex_cache_arrays_.clear();
       {
         ReaderMutexLock mu(self, *class_linker->DexLock());
-        for (size_t i = 0; i < class_linker->GetDexCacheCount(); ++i) {
-          auto* dex_cache = class_linker->GetDexCache(i);
-          dex_cache_arrays_.insert(dex_cache->GetResolvedFields());
-          dex_cache_arrays_.insert(dex_cache->GetResolvedMethods());
+        for (jobject weak_root : class_linker->GetDexCaches()) {
+          mirror::DexCache* dex_cache =
+              down_cast<mirror::DexCache*>(self->DecodeJObject(weak_root));
+          if (dex_cache != nullptr) {
+            dex_cache_arrays_.insert(dex_cache->GetResolvedFields());
+            dex_cache_arrays_.insert(dex_cache->GetResolvedMethods());
+          }
         }
       }
       ReaderMutexLock mu(self, *Locks::heap_bitmap_lock_);
@@ -2330,21 +2352,17 @@
   ScopedObjectAccess soa(self);
   ClassLinker* class_linker = runtime->GetClassLinker();
   class_linker->RegisterOatFile(oat_file);
-  std::vector<std::unique_ptr<const DexFile>> dex_files;
+  std::vector<const DexFile*> class_path;
   for (const OatFile::OatDexFile* odf : oat_file->GetOatDexFiles()) {
     std::string error_msg;
-    std::unique_ptr<const DexFile> dex_file = odf->OpenDexFile(&error_msg);
+    const DexFile* const dex_file = OpenDexFile(odf, &error_msg);
     CHECK(dex_file != nullptr) << error_msg;
     class_linker->RegisterDexFile(*dex_file);
-    dex_files.push_back(std::move(dex_file));
+    class_path.push_back(dex_file);
   }
 
   // Need a class loader.
   // Fake that we're a compiler.
-  std::vector<const DexFile*> class_path;
-  for (auto& dex_file : dex_files) {
-    class_path.push_back(dex_file.get());
-  }
   jobject class_loader = class_linker->CreatePathClassLoader(self, class_path);
 
   // Use the class loader while dumping.
diff --git a/runtime/arch/arm/quick_entrypoints_arm.S b/runtime/arch/arm/quick_entrypoints_arm.S
index f6d954f..d6396c1 100644
--- a/runtime/arch/arm/quick_entrypoints_arm.S
+++ b/runtime/arch/arm/quick_entrypoints_arm.S
@@ -609,7 +609,7 @@
     .cfi_rel_offset lr, 20
     sub sp, #8                      @ push padding
     .cfi_adjust_cfa_offset 8
-    @ mov r0, r0                    @ pass ref in r0 (no-op for now since parameter ref is unused)
+    @ mov r0, \rRef                 @ pass ref in r0 (no-op for now since parameter ref is unused)
     .ifnc \rObj, r1
         mov r1, \rObj               @ pass rObj
     .endif
diff --git a/runtime/arch/arm64/quick_entrypoints_arm64.S b/runtime/arch/arm64/quick_entrypoints_arm64.S
index 8ba3d43..bfef0fa 100644
--- a/runtime/arch/arm64/quick_entrypoints_arm64.S
+++ b/runtime/arch/arm64/quick_entrypoints_arm64.S
@@ -1146,7 +1146,7 @@
     .cfi_rel_offset x4, 32
     .cfi_rel_offset x30, 40
 
-    // mov x0, x0                   // pass ref in x0 (no-op for now since parameter ref is unused)
+    // mov x0, \xRef                // pass ref in x0 (no-op for now since parameter ref is unused)
     .ifnc \xObj, x1
         mov x1, \xObj               // pass xObj
     .endif
diff --git a/runtime/arch/mips/quick_entrypoints_mips.S b/runtime/arch/mips/quick_entrypoints_mips.S
index 8bc75e5..cb49cf5 100644
--- a/runtime/arch/mips/quick_entrypoints_mips.S
+++ b/runtime/arch/mips/quick_entrypoints_mips.S
@@ -869,7 +869,7 @@
     sw     $a0, 0($sp)
     .cfi_rel_offset 4, 0
 
-    # move $a0, $a0                 # pass ref in a0 (no-op for now since parameter ref is unused)
+    # move $a0, \rRef               # pass ref in a0 (no-op for now since parameter ref is unused)
     .ifnc \rObj, $a1
         move $a1, \rObj             # pass rObj
     .endif
diff --git a/runtime/arch/mips64/quick_entrypoints_mips64.S b/runtime/arch/mips64/quick_entrypoints_mips64.S
index c30e6ca..4bc049c 100644
--- a/runtime/arch/mips64/quick_entrypoints_mips64.S
+++ b/runtime/arch/mips64/quick_entrypoints_mips64.S
@@ -922,7 +922,7 @@
     sd     $a0, 0($sp)
     .cfi_rel_offset 4, 0
 
-    # move $a0, $a0                 # pass ref in a0 (no-op for now since parameter ref is unused)
+    # move $a0, \rRef               # pass ref in a0 (no-op for now since parameter ref is unused)
     .ifnc \rObj, $a1
         move $a1, \rObj             # pass rObj
     .endif
diff --git a/runtime/arch/x86/quick_entrypoints_x86.S b/runtime/arch/x86/quick_entrypoints_x86.S
index 1da5a2f..9b2d59d 100644
--- a/runtime/arch/x86/quick_entrypoints_x86.S
+++ b/runtime/arch/x86/quick_entrypoints_x86.S
@@ -1200,9 +1200,9 @@
     READ_BARRIER edx, MIRROR_OBJECT_CLASS_OFFSET, eax, false
     cmpl %eax, %ebx
     POP eax                      // restore eax from the push in the beginning of READ_BARRIER macro
+    // This asymmetric push/pop saves a push of eax and maintains stack alignment.
 #elif defined(USE_HEAP_POISONING)
     PUSH eax                     // save eax
-    // Cannot call READ_BARRIER macro here, because the above push messes up stack alignment.
     movl MIRROR_OBJECT_CLASS_OFFSET(%edx), %eax
     UNPOISON_HEAP_REF eax
     cmpl %eax, %ebx
@@ -1225,15 +1225,22 @@
     PUSH eax                      // save arguments
     PUSH ecx
     PUSH edx
+#if defined(USE_READ_BARRIER)
+    subl LITERAL(4), %esp         // alignment padding
+    CFI_ADJUST_CFA_OFFSET(4)
+    READ_BARRIER edx, MIRROR_OBJECT_CLASS_OFFSET, eax, true
+    subl LITERAL(4), %esp         // alignment padding
+    CFI_ADJUST_CFA_OFFSET(4)
+    PUSH eax                      // pass arg2 - type of the value to be stored
+#elif defined(USE_HEAP_POISONING)
     subl LITERAL(8), %esp         // alignment padding
     CFI_ADJUST_CFA_OFFSET(8)
-#ifdef USE_HEAP_POISONING
-    // This load does not need read barrier, since edx is unchanged and there's no GC safe point
-    // from last read of MIRROR_OBJECT_CLASS_OFFSET(%edx).
-    movl MIRROR_OBJECT_CLASS_OFFSET(%edx), %eax  // pass arg2 - type of the value to be stored
+    movl MIRROR_OBJECT_CLASS_OFFSET(%edx), %eax
     UNPOISON_HEAP_REF eax
-    PUSH eax
+    PUSH eax                      // pass arg2 - type of the value to be stored
 #else
+    subl LITERAL(8), %esp         // alignment padding
+    CFI_ADJUST_CFA_OFFSET(8)
     pushl MIRROR_OBJECT_CLASS_OFFSET(%edx)  // pass arg2 - type of the value to be stored
     CFI_ADJUST_CFA_OFFSET(4)
 #endif
diff --git a/runtime/arch/x86_64/quick_entrypoints_x86_64.S b/runtime/arch/x86_64/quick_entrypoints_x86_64.S
index f4c9488..88270d9 100644
--- a/runtime/arch/x86_64/quick_entrypoints_x86_64.S
+++ b/runtime/arch/x86_64/quick_entrypoints_x86_64.S
@@ -918,6 +918,13 @@
     // RDI: uint32_t type_idx, RSI: ArtMethod*
     // RDX, RCX, R8, R9: free. RAX: return val.
     // TODO: Add read barrier when this function is used.
+    // Note this function can/should implement read barrier fast path only
+    // (no read barrier slow path) because this is the fast path of tlab allocation.
+    // We can fall back to the allocation slow path to do the read barrier slow path.
+#if defined(USE_READ_BARRIER)
+    int3
+    int3
+#endif
     // Might need a special macro since rsi and edx is 32b/64b mismatched.
     movl ART_METHOD_DEX_CACHE_TYPES_OFFSET(%rsi), %edx  // Load dex cache resolved types array
     UNPOISON_HEAP_REF edx
@@ -1165,7 +1172,7 @@
 END_MACRO
 
     /*
-     * Macro to insert read barrier, used in art_quick_aput_obj and art_quick_alloc_object_tlab.
+     * Macro to insert read barrier, used in art_quick_aput_obj.
      * obj_reg and dest_reg{32|64} are registers, offset is a defined literal such as
      * MIRROR_OBJECT_CLASS_OFFSET. dest_reg needs two versions to handle the mismatch between
      * 64b PUSH/POP and 32b argument.
@@ -1182,8 +1189,8 @@
     PUSH rcx
     SETUP_FP_CALLEE_SAVE_FRAME
     // Outgoing argument set up
-    // movl %edi, %edi                  // pass ref, no-op for now since parameter ref is unused
-    // // movq %rdi, %rdi
+    // movl REG_VAR(ref_reg32), %edi    // pass ref, no-op for now since parameter ref is unused
+    // // movq REG_VAR(ref_reg64), %rdi
     movl REG_VAR(obj_reg), %esi         // pass obj_reg
     // movq REG_VAR(obj_reg), %rsi
     movl MACRO_LITERAL((RAW_VAR(offset))), %edx // pass offset, double parentheses are necessary
diff --git a/runtime/base/hash_set_test.cc b/runtime/base/hash_set_test.cc
index 4ef1f9e..6d2c5e0 100644
--- a/runtime/base/hash_set_test.cc
+++ b/runtime/base/hash_set_test.cc
@@ -17,9 +17,11 @@
 #include "hash_set.h"
 
 #include <map>
+#include <forward_list>
 #include <sstream>
 #include <string>
 #include <unordered_set>
+#include <vector>
 
 #include <gtest/gtest.h>
 #include "hash_map.h"
@@ -258,4 +260,59 @@
   ASSERT_EQ(it->second, 124);
 }
 
+struct IsEmptyFnVectorInt {
+  void MakeEmpty(std::vector<int>& item) const {
+    item.clear();
+  }
+  bool IsEmpty(const std::vector<int>& item) const {
+    return item.empty();
+  }
+};
+
+template <typename T>
+size_t HashIntSequence(T begin, T end) {
+  size_t hash = 0;
+  for (auto iter = begin; iter != end; ++iter) {
+    hash = hash * 2 + *iter;
+  }
+  return hash;
+};
+
+struct VectorIntHashEquals {
+  std::size_t operator()(const std::vector<int>& item) const {
+    return HashIntSequence(item.begin(), item.end());
+  }
+
+  std::size_t operator()(const std::forward_list<int>& item) const {
+    return HashIntSequence(item.begin(), item.end());
+  }
+
+  bool operator()(const std::vector<int>& a, const std::vector<int>& b) const {
+    return a == b;
+  }
+
+  bool operator()(const std::vector<int>& a, const std::forward_list<int>& b) const {
+    auto aiter = a.begin();
+    auto biter = b.begin();
+    while (aiter != a.end() && biter != b.end()) {
+      if (*aiter != *biter) {
+        return false;
+      }
+      aiter++;
+      biter++;
+    }
+    return (aiter == a.end() && biter == b.end());
+  }
+};
+
+TEST_F(HashSetTest, TestLookupByAlternateKeyType) {
+  HashSet<std::vector<int>, IsEmptyFnVectorInt, VectorIntHashEquals, VectorIntHashEquals> hash_set;
+  hash_set.Insert(std::vector<int>({1, 2, 3, 4}));
+  hash_set.Insert(std::vector<int>({4, 2}));
+  ASSERT_EQ(hash_set.end(), hash_set.Find(std::vector<int>({1, 1, 1, 1})));
+  ASSERT_NE(hash_set.end(), hash_set.Find(std::vector<int>({1, 2, 3, 4})));
+  ASSERT_EQ(hash_set.end(), hash_set.Find(std::forward_list<int>({1, 1, 1, 1})));
+  ASSERT_NE(hash_set.end(), hash_set.Find(std::forward_list<int>({1, 2, 3, 4})));
+}
+
 }  // namespace art
diff --git a/runtime/base/mutex.h b/runtime/base/mutex.h
index 848c904..6bf203c 100644
--- a/runtime/base/mutex.h
+++ b/runtime/base/mutex.h
@@ -64,6 +64,8 @@
   kJdwpSocketLock,
   kRegionSpaceRegionLock,
   kTransactionLogLock,
+  kMarkSweepMarkStackLock,
+  kJniWeakGlobalsLock,
   kReferenceQueueSoftReferencesLock,
   kReferenceQueuePhantomReferencesLock,
   kReferenceQueueFinalizerReferencesLock,
@@ -79,7 +81,6 @@
   kArenaPoolLock,
   kDexFileMethodInlinerLock,
   kDexFileToMethodInlinerMapLock,
-  kMarkSweepMarkStackLock,
   kInternTableLock,
   kOatFileSecondaryLookupLock,
   kTracingUniqueMethodsLock,
diff --git a/runtime/class_linker-inl.h b/runtime/class_linker-inl.h
index 11901b3..d2dbff6 100644
--- a/runtime/class_linker-inl.h
+++ b/runtime/class_linker-inl.h
@@ -195,12 +195,6 @@
   return klass;
 }
 
-inline mirror::DexCache* ClassLinker::GetDexCache(size_t idx) {
-  dex_lock_.AssertSharedHeld(Thread::Current());
-  DCHECK(idx < dex_caches_.size());
-  return dex_caches_[idx].Read();
-}
-
 }  // namespace art
 
 #endif  // ART_RUNTIME_CLASS_LINKER_INL_H_
diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc
index dc273d8..e78914c 100644
--- a/runtime/class_linker.cc
+++ b/runtime/class_linker.cc
@@ -273,7 +273,6 @@
       array_iftable_(nullptr),
       find_array_class_cache_next_victim_(0),
       init_done_(false),
-      log_new_dex_caches_roots_(false),
       log_new_class_table_roots_(false),
       intern_table_(intern_table),
       quick_resolution_trampoline_(nullptr),
@@ -332,6 +331,12 @@
   java_lang_Class->SetSuperClass(java_lang_Object.Get());
   mirror::Class::SetStatus(java_lang_Object, mirror::Class::kStatusLoaded, self);
 
+  java_lang_Object->SetObjectSize(sizeof(mirror::Object));
+  runtime->SetSentinel(heap->AllocObject<true>(self,
+                                               java_lang_Object.Get(),
+                                               java_lang_Object->GetObjectSize(),
+                                               VoidFunctor()));
+
   // Object[] next to hold class roots.
   Handle<mirror::Class> object_array_class(hs.NewHandle(
       AllocClass(self, java_lang_Class.Get(),
@@ -570,12 +575,16 @@
   CHECK_EQ(java_lang_ref_Reference->GetClassSize(),
            mirror::Reference::ClassSize(image_pointer_size_));
   class_root = FindSystemClass(self, "Ljava/lang/ref/FinalizerReference;");
+  CHECK_EQ(class_root->GetClassFlags(), mirror::kClassFlagNormal);
   class_root->SetClassFlags(class_root->GetClassFlags() | mirror::kClassFlagFinalizerReference);
   class_root = FindSystemClass(self, "Ljava/lang/ref/PhantomReference;");
+  CHECK_EQ(class_root->GetClassFlags(), mirror::kClassFlagNormal);
   class_root->SetClassFlags(class_root->GetClassFlags() | mirror::kClassFlagPhantomReference);
   class_root = FindSystemClass(self, "Ljava/lang/ref/SoftReference;");
+  CHECK_EQ(class_root->GetClassFlags(), mirror::kClassFlagNormal);
   class_root->SetClassFlags(class_root->GetClassFlags() | mirror::kClassFlagSoftReference);
   class_root = FindSystemClass(self, "Ljava/lang/ref/WeakReference;");
+  CHECK_EQ(class_root->GetClassFlags(), mirror::kClassFlagNormal);
   class_root->SetClassFlags(class_root->GetClassFlags() | mirror::kClassFlagWeakReference);
 
   // Setup the ClassLoader, verifying the object_size_.
@@ -1139,11 +1148,11 @@
   quick_imt_conflict_trampoline_ = oat_file.GetOatHeader().GetQuickImtConflictTrampoline();
   quick_generic_jni_trampoline_ = oat_file.GetOatHeader().GetQuickGenericJniTrampoline();
   quick_to_interpreter_bridge_trampoline_ = oat_file.GetOatHeader().GetQuickToInterpreterBridge();
+  StackHandleScope<2> hs(self);
   mirror::Object* dex_caches_object = space->GetImageHeader().GetImageRoot(ImageHeader::kDexCaches);
-  mirror::ObjectArray<mirror::DexCache>* dex_caches =
-      dex_caches_object->AsObjectArray<mirror::DexCache>();
+  Handle<mirror::ObjectArray<mirror::DexCache>> dex_caches(
+      hs.NewHandle(dex_caches_object->AsObjectArray<mirror::DexCache>()));
 
-  StackHandleScope<1> hs(self);
   Handle<mirror::ObjectArray<mirror::Class>> class_roots(hs.NewHandle(
           space->GetImageHeader().GetImageRoot(ImageHeader::kClassRoots)->
           AsObjectArray<mirror::Class>()));
@@ -1153,6 +1162,13 @@
   // as being Strings or not
   mirror::String::SetClass(GetClassRoot(kJavaLangString));
 
+  mirror::Class* java_lang_Object = GetClassRoot(kJavaLangObject);
+  java_lang_Object->SetObjectSize(sizeof(mirror::Object));
+  Runtime::Current()->SetSentinel(Runtime::Current()->GetHeap()->AllocObject<true>(self,
+                                                          java_lang_Object,
+                                                          java_lang_Object->GetObjectSize(),
+                                                          VoidFunctor()));
+
   CHECK_EQ(oat_file.GetOatHeader().GetDexFileCount(),
            static_cast<uint32_t>(dex_caches->GetLength()));
   for (int32_t i = 0; i < dex_caches->GetLength(); i++) {
@@ -1246,7 +1262,6 @@
 }
 
 bool ClassLinker::ClassInClassTable(mirror::Class* klass) {
-  ReaderMutexLock mu(Thread::Current(), *Locks::classlinker_classes_lock_);
   ClassTable* const class_table = ClassTableForClassLoader(klass->GetClassLoader());
   return class_table != nullptr && class_table->Contains(klass);
 }
@@ -1303,27 +1318,6 @@
 // mapped image.
 void ClassLinker::VisitRoots(RootVisitor* visitor, VisitRootFlags flags) {
   class_roots_.VisitRootIfNonNull(visitor, RootInfo(kRootVMInternal));
-  Thread* const self = Thread::Current();
-  {
-    ReaderMutexLock mu(self, dex_lock_);
-    if ((flags & kVisitRootFlagAllRoots) != 0) {
-      for (GcRoot<mirror::DexCache>& dex_cache : dex_caches_) {
-        dex_cache.VisitRoot(visitor, RootInfo(kRootVMInternal));
-      }
-    } else if ((flags & kVisitRootFlagNewRoots) != 0) {
-      for (size_t index : new_dex_cache_roots_) {
-        dex_caches_[index].VisitRoot(visitor, RootInfo(kRootVMInternal));
-      }
-    }
-    if ((flags & kVisitRootFlagClearRootLog) != 0) {
-      new_dex_cache_roots_.clear();
-    }
-    if ((flags & kVisitRootFlagStartLoggingNewRoots) != 0) {
-      log_new_dex_caches_roots_ = true;
-    } else if ((flags & kVisitRootFlagStopLoggingNewRoots) != 0) {
-      log_new_dex_caches_roots_ = false;
-    }
-  }
   VisitClassRoots(visitor, flags);
   array_iftable_.VisitRootIfNonNull(visitor, RootInfo(kRootVMInternal));
   for (GcRoot<mirror::Class>& root : find_array_class_cache_) {
@@ -1698,7 +1692,6 @@
                 long_array->GetWithoutChecks(j)));
             const DexFile::ClassDef* dex_class_def = cp_dex_file->FindClassDef(descriptor, hash);
             if (dex_class_def != nullptr) {
-              RegisterDexFile(*cp_dex_file);
               mirror::Class* klass = DefineClass(self, descriptor, hash, class_loader,
                                                  *cp_dex_file, *dex_class_def);
               if (klass == nullptr) {
@@ -1841,11 +1834,15 @@
     klass.Assign(AllocClass(self, SizeOfClassWithoutEmbeddedTables(dex_file, dex_class_def)));
   }
   if (UNLIKELY(klass.Get() == nullptr)) {
-    CHECK(self->IsExceptionPending());  // Expect an OOME.
+    self->AssertPendingOOMException();
     return nullptr;
   }
-  klass->SetDexCache(FindDexCache(dex_file));
-
+  mirror::DexCache* dex_cache = RegisterDexFile(dex_file);
+  if (dex_cache == nullptr) {
+    self->AssertPendingOOMException();
+    return nullptr;
+  }
+  klass->SetDexCache(dex_cache);
   SetupClass(dex_file, dex_class_def, klass, class_loader.Get());
 
   // Mark the string class by setting its access flag.
@@ -2478,58 +2475,54 @@
   RegisterDexFile(dex_file, dex_cache);
 }
 
-bool ClassLinker::IsDexFileRegisteredLocked(const DexFile& dex_file) {
-  dex_lock_.AssertSharedHeld(Thread::Current());
-  for (GcRoot<mirror::DexCache>& root : dex_caches_) {
-    mirror::DexCache* dex_cache = root.Read();
-    if (dex_cache->GetDexFile() == &dex_file) {
-      return true;
-    }
-  }
-  return false;
-}
-
-bool ClassLinker::IsDexFileRegistered(const DexFile& dex_file) {
-  ReaderMutexLock mu(Thread::Current(), dex_lock_);
-  return IsDexFileRegisteredLocked(dex_file);
-}
-
 void ClassLinker::RegisterDexFileLocked(const DexFile& dex_file,
                                         Handle<mirror::DexCache> dex_cache) {
-  dex_lock_.AssertExclusiveHeld(Thread::Current());
+  Thread* const self = Thread::Current();
+  dex_lock_.AssertExclusiveHeld(self);
   CHECK(dex_cache.Get() != nullptr) << dex_file.GetLocation();
   CHECK(dex_cache->GetLocation()->Equals(dex_file.GetLocation()))
       << dex_cache->GetLocation()->ToModifiedUtf8() << " " << dex_file.GetLocation();
-  dex_caches_.push_back(GcRoot<mirror::DexCache>(dex_cache.Get()));
-  dex_cache->SetDexFile(&dex_file);
-  if (log_new_dex_caches_roots_) {
-    // TODO: This is not safe if we can remove dex caches.
-    new_dex_cache_roots_.push_back(dex_caches_.size() - 1);
+  // Clean up pass to remove null dex caches.
+  // Null dex caches can occur due to class unloading and we are lazily removing null entries.
+  JavaVMExt* const vm = self->GetJniEnv()->vm;
+  for (auto it = dex_caches_.begin(); it != dex_caches_.end();) {
+    mirror::Object* dex_cache_root = self->DecodeJObject(*it);
+    if (dex_cache_root == nullptr) {
+      vm->DeleteWeakGlobalRef(self, *it);
+      it = dex_caches_.erase(it);
+    } else {
+      ++it;
+    }
   }
+  dex_caches_.push_back(vm->AddWeakGlobalRef(self, dex_cache.Get()));
+  dex_cache->SetDexFile(&dex_file);
 }
 
-void ClassLinker::RegisterDexFile(const DexFile& dex_file) {
+mirror::DexCache* ClassLinker::RegisterDexFile(const DexFile& dex_file) {
   Thread* self = Thread::Current();
   {
     ReaderMutexLock mu(self, dex_lock_);
-    if (IsDexFileRegisteredLocked(dex_file)) {
-      return;
+    mirror::DexCache* dex_cache = FindDexCacheLocked(self, dex_file, true);
+    if (dex_cache != nullptr) {
+      return dex_cache;
     }
   }
   // Don't alloc while holding the lock, since allocation may need to
   // suspend all threads and another thread may need the dex_lock_ to
   // get to a suspend point.
   StackHandleScope<1> hs(self);
-  Handle<mirror::DexCache> dex_cache(hs.NewHandle(AllocDexCache(self, dex_file)));
-  CHECK(dex_cache.Get() != nullptr) << "Failed to allocate dex cache for "
-                                    << dex_file.GetLocation();
-  {
-    WriterMutexLock mu(self, dex_lock_);
-    if (IsDexFileRegisteredLocked(dex_file)) {
-      return;
-    }
-    RegisterDexFileLocked(dex_file, dex_cache);
+  Handle<mirror::DexCache> h_dex_cache(hs.NewHandle(AllocDexCache(self, dex_file)));
+  WriterMutexLock mu(self, dex_lock_);
+  mirror::DexCache* dex_cache = FindDexCacheLocked(self, dex_file, true);
+  if (dex_cache != nullptr) {
+    return dex_cache;
   }
+  if (h_dex_cache.Get() == nullptr) {
+    self->AssertPendingOOMException();
+    return nullptr;
+  }
+  RegisterDexFileLocked(dex_file, h_dex_cache);
+  return h_dex_cache.Get();
 }
 
 void ClassLinker::RegisterDexFile(const DexFile& dex_file,
@@ -2538,36 +2531,52 @@
   RegisterDexFileLocked(dex_file, dex_cache);
 }
 
-mirror::DexCache* ClassLinker::FindDexCache(const DexFile& dex_file) {
-  ReaderMutexLock mu(Thread::Current(), dex_lock_);
+mirror::DexCache* ClassLinker::FindDexCache(Thread* self,
+                                            const DexFile& dex_file,
+                                            bool allow_failure) {
+  ReaderMutexLock mu(self, dex_lock_);
+  return FindDexCacheLocked(self, dex_file, allow_failure);
+}
+
+mirror::DexCache* ClassLinker::FindDexCacheLocked(Thread* self,
+                                                  const DexFile& dex_file,
+                                                  bool allow_failure) {
   // Search assuming unique-ness of dex file.
-  for (size_t i = 0; i != dex_caches_.size(); ++i) {
-    mirror::DexCache* dex_cache = GetDexCache(i);
-    if (dex_cache->GetDexFile() == &dex_file) {
-      return dex_cache;
+  JavaVMExt* const vm = self->GetJniEnv()->vm;
+  {
+    MutexLock mu(self, vm->WeakGlobalsLock());
+    for (jobject weak_root : dex_caches_) {
+      DCHECK_EQ(GetIndirectRefKind(weak_root), kWeakGlobal);
+      mirror::DexCache* dex_cache = down_cast<mirror::DexCache*>(
+          vm->DecodeWeakGlobalLocked(self, weak_root));
+      if (dex_cache != nullptr && dex_cache->GetDexFile() == &dex_file) {
+        return dex_cache;
+      }
     }
   }
-  // Search matching by location name.
+  if (allow_failure) {
+    return nullptr;
+  }
   std::string location(dex_file.GetLocation());
-  for (size_t i = 0; i != dex_caches_.size(); ++i) {
-    mirror::DexCache* dex_cache = GetDexCache(i);
-    if (dex_cache->GetDexFile()->GetLocation() == location) {
-      return dex_cache;
-    }
-  }
   // Failure, dump diagnostic and abort.
-  for (size_t i = 0; i != dex_caches_.size(); ++i) {
-    mirror::DexCache* dex_cache = GetDexCache(i);
-    LOG(ERROR) << "Registered dex file " << i << " = " << dex_cache->GetDexFile()->GetLocation();
+  for (jobject weak_root : dex_caches_) {
+    mirror::DexCache* dex_cache = down_cast<mirror::DexCache*>(self->DecodeJObject(weak_root));
+    if (dex_cache != nullptr) {
+      LOG(ERROR) << "Registered dex file " << dex_cache->GetDexFile()->GetLocation();
+    }
   }
   LOG(FATAL) << "Failed to find DexCache for DexFile " << location;
   UNREACHABLE();
 }
 
 void ClassLinker::FixupDexCaches(ArtMethod* resolution_method) {
-  ReaderMutexLock mu(Thread::Current(), dex_lock_);
-  for (auto& dex_cache : dex_caches_) {
-    dex_cache.Read()->Fixup(resolution_method, image_pointer_size_);
+  Thread* const self = Thread::Current();
+  ReaderMutexLock mu(self, dex_lock_);
+  for (jobject weak_root : dex_caches_) {
+    mirror::DexCache* dex_cache = down_cast<mirror::DexCache*>(self->DecodeJObject(weak_root));
+    if (dex_cache != nullptr) {
+      dex_cache->Fixup(resolution_method, image_pointer_size_);
+    }
   }
 }
 
@@ -3403,11 +3412,13 @@
   DCHECK(proxy_class->IsProxyClass());
   DCHECK(proxy_method->IsProxyMethod());
   {
-    ReaderMutexLock mu(Thread::Current(), dex_lock_);
+    Thread* const self = Thread::Current();
+    ReaderMutexLock mu(self, dex_lock_);
     // Locate the dex cache of the original interface/Object
-    for (const GcRoot<mirror::DexCache>& root : dex_caches_) {
-      auto* dex_cache = root.Read();
-      if (proxy_method->HasSameDexCacheResolvedTypes(dex_cache->GetResolvedTypes())) {
+    for (jobject weak_root : dex_caches_) {
+      mirror::DexCache* dex_cache = down_cast<mirror::DexCache*>(self->DecodeJObject(weak_root));
+      if (dex_cache != nullptr &&
+          proxy_method->HasSameDexCacheResolvedTypes(dex_cache->GetResolvedTypes())) {
         ArtMethod* resolved_method = dex_cache->GetResolvedMethod(
             proxy_method->GetDexMethodIndex(), image_pointer_size_);
         CHECK(resolved_method != nullptr);
@@ -4387,8 +4398,9 @@
   }
 
   // Inherit reference flags (if any) from the superclass.
-  int reference_flags = (super->GetClassFlags() & mirror::kClassFlagReference);
+  uint32_t reference_flags = (super->GetClassFlags() & mirror::kClassFlagReference);
   if (reference_flags != 0) {
+    CHECK_EQ(klass->GetClassFlags(), 0u);
     klass->SetClassFlags(klass->GetClassFlags() | reference_flags);
   }
   // Disallow custom direct subclasses of java.lang.ref.Reference.
@@ -5232,17 +5244,26 @@
     mirror::Class* super_class = klass->GetSuperClass();
     if (num_reference_fields == 0 || super_class == nullptr) {
       // object has one reference field, klass, but we ignore it since we always visit the class.
-      // If the super_class is null then we are java.lang.Object.
+      // super_class is null iff the class is java.lang.Object.
       if (super_class == nullptr ||
           (super_class->GetClassFlags() & mirror::kClassFlagNoReferenceFields) != 0) {
         klass->SetClassFlags(klass->GetClassFlags() | mirror::kClassFlagNoReferenceFields);
-      } else if (kIsDebugBuild) {
-        size_t total_reference_instance_fields = 0;
-        while (super_class != nullptr) {
-          total_reference_instance_fields += super_class->NumReferenceInstanceFields();
-          super_class = super_class->GetSuperClass();
-        }
-        CHECK_GT(total_reference_instance_fields, 1u);
+      }
+    }
+    if (kIsDebugBuild) {
+      DCHECK_EQ(super_class == nullptr, klass->DescriptorEquals("Ljava/lang/Object;"));
+      size_t total_reference_instance_fields = 0;
+      mirror::Class* cur_super = klass.Get();
+      while (cur_super != nullptr) {
+        total_reference_instance_fields += cur_super->NumReferenceInstanceFieldsDuringLinking();
+        cur_super = cur_super->GetSuperClass();
+      }
+      if (super_class == nullptr) {
+        CHECK_EQ(total_reference_instance_fields, 1u) << PrettyDescriptor(klass.Get());
+      } else {
+        // Check that there is at least num_reference_fields other than Object.class.
+        CHECK_GE(total_reference_instance_fields, 1u + num_reference_fields)
+            << PrettyClass(klass.Get());
       }
     }
     if (!klass->IsVariableSize()) {
@@ -5864,11 +5885,6 @@
   // We could move the jobject to the callers, but all call-sites do this...
   ScopedObjectAccessUnchecked soa(self);
 
-  // Register the dex files.
-  for (const DexFile* dex_file : dex_files) {
-    RegisterDexFile(*dex_file);
-  }
-
   // For now, create a libcore-level DexFile for each ART DexFile. This "explodes" multidex.
   StackHandleScope<10> hs(self);
 
diff --git a/runtime/class_linker.h b/runtime/class_linker.h
index fbf4035..2a7162b 100644
--- a/runtime/class_linker.h
+++ b/runtime/class_linker.h
@@ -278,7 +278,7 @@
   void RunRootClinits() SHARED_REQUIRES(Locks::mutator_lock_)
       REQUIRES(!dex_lock_, !Roles::uninterruptible_);
 
-  void RegisterDexFile(const DexFile& dex_file)
+  mirror::DexCache* RegisterDexFile(const DexFile& dex_file)
       REQUIRES(!dex_lock_) SHARED_REQUIRES(Locks::mutator_lock_);
   void RegisterDexFile(const DexFile& dex_file, Handle<mirror::DexCache> dex_cache)
       REQUIRES(!dex_lock_) SHARED_REQUIRES(Locks::mutator_lock_);
@@ -309,9 +309,9 @@
   void VisitRoots(RootVisitor* visitor, VisitRootFlags flags)
       REQUIRES(!dex_lock_) SHARED_REQUIRES(Locks::mutator_lock_);
 
-  mirror::DexCache* FindDexCache(const DexFile& dex_file)
-      REQUIRES(!dex_lock_) SHARED_REQUIRES(Locks::mutator_lock_);
-  bool IsDexFileRegistered(const DexFile& dex_file)
+  mirror::DexCache* FindDexCache(Thread* self,
+                                 const DexFile& dex_file,
+                                 bool allow_failure = false)
       REQUIRES(!dex_lock_) SHARED_REQUIRES(Locks::mutator_lock_);
   void FixupDexCaches(ArtMethod* resolution_method)
       REQUIRES(!dex_lock_) SHARED_REQUIRES(Locks::mutator_lock_);
@@ -471,7 +471,7 @@
 
   // Used by image writer for checking.
   bool ClassInClassTable(mirror::Class* klass)
-      REQUIRES(!Locks::classlinker_classes_lock_)
+      REQUIRES(Locks::classlinker_classes_lock_)
       SHARED_REQUIRES(Locks::mutator_lock_);
 
   ArtMethod* CreateRuntimeMethod();
@@ -561,8 +561,11 @@
 
   void RegisterDexFileLocked(const DexFile& dex_file, Handle<mirror::DexCache> dex_cache)
       REQUIRES(dex_lock_) SHARED_REQUIRES(Locks::mutator_lock_);
-  bool IsDexFileRegisteredLocked(const DexFile& dex_file)
-      SHARED_REQUIRES(dex_lock_, Locks::mutator_lock_);
+  mirror::DexCache* FindDexCacheLocked(Thread* self,
+                                       const DexFile& dex_file,
+                                       bool allow_failure)
+      REQUIRES(dex_lock_)
+      SHARED_REQUIRES(Locks::mutator_lock_);
 
   bool InitializeClass(Thread* self, Handle<mirror::Class> klass, bool can_run_clinit,
                        bool can_init_parents)
@@ -631,7 +634,9 @@
   size_t GetDexCacheCount() SHARED_REQUIRES(Locks::mutator_lock_, dex_lock_) {
     return dex_caches_.size();
   }
-  mirror::DexCache* GetDexCache(size_t idx) SHARED_REQUIRES(Locks::mutator_lock_, dex_lock_);
+  const std::list<jobject>& GetDexCaches() SHARED_REQUIRES(Locks::mutator_lock_, dex_lock_) {
+    return dex_caches_;
+  }
 
   const OatFile* FindOpenedOatFileFromOatLocation(const std::string& oat_location)
       REQUIRES(!dex_lock_);
@@ -702,8 +707,9 @@
   std::vector<std::unique_ptr<const DexFile>> opened_dex_files_;
 
   mutable ReaderWriterMutex dex_lock_ DEFAULT_MUTEX_ACQUIRED_AFTER;
-  std::vector<size_t> new_dex_cache_roots_ GUARDED_BY(dex_lock_);
-  std::vector<GcRoot<mirror::DexCache>> dex_caches_ GUARDED_BY(dex_lock_);
+  // JNI weak globals to allow dex caches to get unloaded. We lazily delete weak globals when we
+  // register new dex files.
+  std::list<jobject> dex_caches_ GUARDED_BY(dex_lock_);
   std::vector<const OatFile*> oat_files_ GUARDED_BY(dex_lock_);
 
   // This contains the class laoders which have class tables. It is populated by
@@ -736,7 +742,6 @@
   size_t find_array_class_cache_next_victim_;
 
   bool init_done_;
-  bool log_new_dex_caches_roots_ GUARDED_BY(dex_lock_);
   bool log_new_class_table_roots_ GUARDED_BY(Locks::classlinker_classes_lock_);
 
   InternTable* intern_table_;
diff --git a/runtime/class_linker_test.cc b/runtime/class_linker_test.cc
index 0d1c875..c3191fa 100644
--- a/runtime/class_linker_test.cc
+++ b/runtime/class_linker_test.cc
@@ -355,7 +355,7 @@
     TestRootVisitor visitor;
     class_linker_->VisitRoots(&visitor, kVisitRootFlagAllRoots);
     // Verify the dex cache has resolution methods in all resolved method slots
-    mirror::DexCache* dex_cache = class_linker_->FindDexCache(dex);
+    mirror::DexCache* dex_cache = class_linker_->FindDexCache(Thread::Current(), dex);
     auto* resolved_methods = dex_cache->GetResolvedMethods();
     for (size_t i = 0; i < static_cast<size_t>(resolved_methods->GetLength()); i++) {
       EXPECT_TRUE(resolved_methods->GetElementPtrSize<ArtMethod*>(i, sizeof(void*)) != nullptr)
diff --git a/runtime/common_runtime_test.cc b/runtime/common_runtime_test.cc
index 5f9e413..56c5d1a 100644
--- a/runtime/common_runtime_test.cc
+++ b/runtime/common_runtime_test.cc
@@ -551,7 +551,8 @@
   }
 
   Thread* self = Thread::Current();
-  jobject class_loader = Runtime::Current()->GetClassLinker()->CreatePathClassLoader(self,                                                                                   class_path);
+  jobject class_loader = Runtime::Current()->GetClassLinker()->CreatePathClassLoader(self,
+                                                                                     class_path);
   self->SetClassLoaderOverride(class_loader);
   return class_loader;
 }
diff --git a/runtime/debugger.cc b/runtime/debugger.cc
index 0cbbb79..8d34f5a 100644
--- a/runtime/debugger.cc
+++ b/runtime/debugger.cc
@@ -1191,6 +1191,10 @@
       if (error != JDWP::ERR_NONE) {
         return error;
       }
+      // Check if the object's type is compatible with the array's type.
+      if (o != nullptr && !o->InstanceOf(oa->GetClass()->GetComponentType())) {
+        return JDWP::ERR_TYPE_MISMATCH;
+      }
       oa->Set<false>(offset + i, o);
     }
   }
@@ -2094,6 +2098,7 @@
     case kWaitingInMainSignalCatcherLoop:
     case kWaitingPerformingGc:
     case kWaitingWeakGcRootRead:
+    case kWaitingForGcThreadFlip:
     case kWaiting:
       return JDWP::TS_WAIT;
       // Don't add a 'default' here so the compiler can spot incompatible enum changes.
diff --git a/runtime/entrypoints/quick/quick_field_entrypoints.cc b/runtime/entrypoints/quick/quick_field_entrypoints.cc
index 0a1d806..7361d34 100644
--- a/runtime/entrypoints/quick/quick_field_entrypoints.cc
+++ b/runtime/entrypoints/quick/quick_field_entrypoints.cc
@@ -558,7 +558,7 @@
 }
 
 // TODO: Currently the read barrier does not have a fast path. Ideally the slow path should only
-// take one parameter "ref", which is generated by the fast path.
+// take one parameter "ref", which is given by the fast path.
 extern "C" mirror::Object* artReadBarrierSlow(mirror::Object* ref ATTRIBUTE_UNUSED,
                                               mirror::Object* obj, uint32_t offset) {
   DCHECK(kUseReadBarrier);
diff --git a/runtime/gc/collector/concurrent_copying.cc b/runtime/gc/collector/concurrent_copying.cc
index 65e946f..a5bc60a 100644
--- a/runtime/gc/collector/concurrent_copying.cc
+++ b/runtime/gc/collector/concurrent_copying.cc
@@ -261,8 +261,10 @@
   gc_barrier_->Init(self, 0);
   ThreadFlipVisitor thread_flip_visitor(this, heap_->use_tlab_);
   FlipCallback flip_callback(this);
+  heap_->ThreadFlipBegin(self);  // Sync with JNI critical calls.
   size_t barrier_count = Runtime::Current()->FlipThreadRoots(
       &thread_flip_visitor, &flip_callback, this);
+  heap_->ThreadFlipEnd(self);
   {
     ScopedThreadStateChange tsc(self, kWaitingForCheckPointsToRun);
     gc_barrier_->Increment(self, barrier_count);
diff --git a/runtime/gc/heap.cc b/runtime/gc/heap.cc
index b8c4478..aec8d63 100644
--- a/runtime/gc/heap.cc
+++ b/runtime/gc/heap.cc
@@ -161,6 +161,8 @@
       zygote_creation_lock_("zygote creation lock", kZygoteCreationLock),
       zygote_space_(nullptr),
       large_object_threshold_(large_object_threshold),
+      disable_thread_flip_count_(0),
+      thread_flip_running_(false),
       collector_type_running_(kCollectorTypeNone),
       last_gc_type_(collector::kGcTypeNone),
       next_gc_type_(collector::kGcTypePartial),
@@ -480,6 +482,9 @@
   gc_complete_lock_ = new Mutex("GC complete lock");
   gc_complete_cond_.reset(new ConditionVariable("GC complete condition variable",
                                                 *gc_complete_lock_));
+  thread_flip_lock_ = new Mutex("GC thread flip lock");
+  thread_flip_cond_.reset(new ConditionVariable("GC thread flip condition variable",
+                                                *thread_flip_lock_));
   task_processor_.reset(new TaskProcessor());
   reference_processor_.reset(new ReferenceProcessor());
   pending_task_lock_ = new Mutex("Pending task lock");
@@ -770,6 +775,71 @@
   --disable_moving_gc_count_;
 }
 
+void Heap::IncrementDisableThreadFlip(Thread* self) {
+  // Supposed to be called by mutators. If thread_flip_running_ is true, block. Otherwise, go ahead.
+  CHECK(kUseReadBarrier);
+  ScopedThreadStateChange tsc(self, kWaitingForGcThreadFlip);
+  MutexLock mu(self, *thread_flip_lock_);
+  bool has_waited = false;
+  uint64_t wait_start = NanoTime();
+  while (thread_flip_running_) {
+    has_waited = true;
+    thread_flip_cond_->Wait(self);
+  }
+  ++disable_thread_flip_count_;
+  if (has_waited) {
+    uint64_t wait_time = NanoTime() - wait_start;
+    total_wait_time_ += wait_time;
+    if (wait_time > long_pause_log_threshold_) {
+      LOG(INFO) << __FUNCTION__ << " blocked for " << PrettyDuration(wait_time);
+    }
+  }
+}
+
+void Heap::DecrementDisableThreadFlip(Thread* self) {
+  // Supposed to be called by mutators. Decrement disable_thread_flip_count_ and potentially wake up
+  // the GC waiting before doing a thread flip.
+  CHECK(kUseReadBarrier);
+  MutexLock mu(self, *thread_flip_lock_);
+  CHECK_GT(disable_thread_flip_count_, 0U);
+  --disable_thread_flip_count_;
+  thread_flip_cond_->Broadcast(self);
+}
+
+void Heap::ThreadFlipBegin(Thread* self) {
+  // Supposed to be called by GC. Set thread_flip_running_ to be true. If disable_thread_flip_count_
+  // > 0, block. Otherwise, go ahead.
+  CHECK(kUseReadBarrier);
+  ScopedThreadStateChange tsc(self, kWaitingForGcThreadFlip);
+  MutexLock mu(self, *thread_flip_lock_);
+  bool has_waited = false;
+  uint64_t wait_start = NanoTime();
+  CHECK(!thread_flip_running_);
+  // Set this to true before waiting so that a new mutator entering a JNI critical won't starve GC.
+  thread_flip_running_ = true;
+  while (disable_thread_flip_count_ > 0) {
+    has_waited = true;
+    thread_flip_cond_->Wait(self);
+  }
+  if (has_waited) {
+    uint64_t wait_time = NanoTime() - wait_start;
+    total_wait_time_ += wait_time;
+    if (wait_time > long_pause_log_threshold_) {
+      LOG(INFO) << __FUNCTION__ << " blocked for " << PrettyDuration(wait_time);
+    }
+  }
+}
+
+void Heap::ThreadFlipEnd(Thread* self) {
+  // Supposed to be called by GC. Set thread_flip_running_ to false and potentially wake up mutators
+  // waiting before doing a JNI critical.
+  CHECK(kUseReadBarrier);
+  MutexLock mu(self, *thread_flip_lock_);
+  CHECK(thread_flip_running_);
+  thread_flip_running_ = false;
+  thread_flip_cond_->Broadcast(self);
+}
+
 void Heap::UpdateProcessState(ProcessState process_state) {
   if (process_state_ != process_state) {
     process_state_ = process_state;
diff --git a/runtime/gc/heap.h b/runtime/gc/heap.h
index d94f109..85688ae 100644
--- a/runtime/gc/heap.h
+++ b/runtime/gc/heap.h
@@ -300,6 +300,12 @@
   void IncrementDisableMovingGC(Thread* self) REQUIRES(!*gc_complete_lock_);
   void DecrementDisableMovingGC(Thread* self) REQUIRES(!*gc_complete_lock_);
 
+  // Temporarily disable thread flip for JNI critical calls.
+  void IncrementDisableThreadFlip(Thread* self) REQUIRES(!*thread_flip_lock_);
+  void DecrementDisableThreadFlip(Thread* self) REQUIRES(!*thread_flip_lock_);
+  void ThreadFlipBegin(Thread* self) REQUIRES(!*thread_flip_lock_);
+  void ThreadFlipEnd(Thread* self) REQUIRES(!*thread_flip_lock_);
+
   // Clear all of the mark bits, doesn't clear bitmaps which have the same live bits as mark bits.
   void ClearMarkedObjects() REQUIRES(Locks::heap_bitmap_lock_);
 
@@ -1065,6 +1071,12 @@
   Mutex* gc_complete_lock_ DEFAULT_MUTEX_ACQUIRED_AFTER;
   std::unique_ptr<ConditionVariable> gc_complete_cond_ GUARDED_BY(gc_complete_lock_);
 
+  // Used to synchronize between JNI critical calls and the thread flip of the CC collector.
+  Mutex* thread_flip_lock_ DEFAULT_MUTEX_ACQUIRED_AFTER;
+  std::unique_ptr<ConditionVariable> thread_flip_cond_ GUARDED_BY(thread_flip_lock_);
+  size_t disable_thread_flip_count_ GUARDED_BY(thread_flip_lock_);
+  bool thread_flip_running_ GUARDED_BY(thread_flip_lock_);
+
   // Reference processor;
   std::unique_ptr<ReferenceProcessor> reference_processor_;
 
diff --git a/runtime/gc/reference_queue_test.cc b/runtime/gc/reference_queue_test.cc
index 888c0d2..ab921d9 100644
--- a/runtime/gc/reference_queue_test.cc
+++ b/runtime/gc/reference_queue_test.cc
@@ -27,11 +27,11 @@
 
 TEST_F(ReferenceQueueTest, EnqueueDequeue) {
   Thread* self = Thread::Current();
+  ScopedObjectAccess soa(self);
   StackHandleScope<20> hs(self);
   Mutex lock("Reference queue lock");
   ReferenceQueue queue(&lock);
   ASSERT_TRUE(queue.IsEmpty());
-  ScopedObjectAccess soa(self);
   ASSERT_EQ(queue.GetLength(), 0U);
   auto ref_class = hs.NewHandle(
       Runtime::Current()->GetClassLinker()->FindClass(self, "Ljava/lang/ref/WeakReference;",
@@ -58,10 +58,10 @@
 
 TEST_F(ReferenceQueueTest, Dump) {
   Thread* self = Thread::Current();
+  ScopedObjectAccess soa(self);
   StackHandleScope<20> hs(self);
   Mutex lock("Reference queue lock");
   ReferenceQueue queue(&lock);
-  ScopedObjectAccess soa(self);
   queue.Dump(LOG(INFO));
   auto weak_ref_class = hs.NewHandle(
       Runtime::Current()->GetClassLinker()->FindClass(self, "Ljava/lang/ref/WeakReference;",
diff --git a/runtime/handle_scope-inl.h b/runtime/handle_scope-inl.h
index 222083b..ca206ef 100644
--- a/runtime/handle_scope-inl.h
+++ b/runtime/handle_scope-inl.h
@@ -19,8 +19,9 @@
 
 #include "handle_scope.h"
 
+#include "base/mutex.h"
 #include "handle.h"
-#include "thread.h"
+#include "thread-inl.h"
 #include "verify_object-inl.h"
 
 namespace art {
@@ -29,6 +30,9 @@
 inline StackHandleScope<kNumReferences>::StackHandleScope(Thread* self, mirror::Object* fill_value)
     : HandleScope(self->GetTopHandleScope(), kNumReferences), self_(self), pos_(0) {
   DCHECK_EQ(self, Thread::Current());
+  if (kDebugLocking) {
+    Locks::mutator_lock_->AssertSharedHeld(Thread::Current());
+  }
   static_assert(kNumReferences >= 1, "StackHandleScope must contain at least 1 reference");
   // TODO: Figure out how to use a compile assert.
   CHECK_EQ(&storage_[0], GetReferences());
@@ -42,6 +46,9 @@
 inline StackHandleScope<kNumReferences>::~StackHandleScope() {
   HandleScope* top_handle_scope = self_->PopHandleScope();
   DCHECK_EQ(top_handle_scope, this);
+  if (kDebugLocking) {
+    Locks::mutator_lock_->AssertSharedHeld(self_);
+  }
 }
 
 inline size_t HandleScope::SizeOf(uint32_t num_references) {
@@ -59,6 +66,9 @@
 
 inline mirror::Object* HandleScope::GetReference(size_t i) const {
   DCHECK_LT(i, number_of_references_);
+  if (kDebugLocking) {
+    Locks::mutator_lock_->AssertSharedHeld(Thread::Current());
+  }
   return GetReferences()[i].AsMirrorPtr();
 }
 
@@ -73,6 +83,9 @@
 }
 
 inline void HandleScope::SetReference(size_t i, mirror::Object* object) {
+  if (kDebugLocking) {
+    Locks::mutator_lock_->AssertSharedHeld(Thread::Current());
+  }
   DCHECK_LT(i, number_of_references_);
   GetReferences()[i].Assign(object);
 }
@@ -104,6 +117,9 @@
 
 template<size_t kNumReferences>
 inline void StackHandleScope<kNumReferences>::SetReference(size_t i, mirror::Object* object) {
+  if (kDebugLocking) {
+    Locks::mutator_lock_->AssertSharedHeld(Thread::Current());
+  }
   DCHECK_LT(i, kNumReferences);
   VerifyObject(object);
   GetReferences()[i].Assign(object);
diff --git a/runtime/hprof/hprof.cc b/runtime/hprof/hprof.cc
index ee6b020..e2094dc 100644
--- a/runtime/hprof/hprof.cc
+++ b/runtime/hprof/hprof.cc
@@ -765,8 +765,9 @@
       okay = !file_output.Errors();
 
       if (okay) {
-        // Check for expected size.
-        CHECK_EQ(file_output.SumLength(), overall_size);
+        // Check for expected size. Output is expected to be less-or-equal than first phase, see
+        // b/23521263.
+        DCHECK_LE(file_output.SumLength(), overall_size);
       }
       output_ = nullptr;
     }
@@ -810,8 +811,8 @@
     // Write the dump.
     ProcessHeap(true);
 
-    // Check for expected size.
-    CHECK_EQ(net_output.SumLength(), overall_size + kChunkHeaderSize);
+    // Check for expected size. See DumpToFile for comment.
+    DCHECK_LE(net_output.SumLength(), overall_size + kChunkHeaderSize);
     output_ = nullptr;
 
     return true;
diff --git a/runtime/java_vm_ext.cc b/runtime/java_vm_ext.cc
index 9d41018..ef7a924 100644
--- a/runtime/java_vm_ext.cc
+++ b/runtime/java_vm_ext.cc
@@ -373,7 +373,7 @@
       globals_(gGlobalsInitial, gGlobalsMax, kGlobal),
       libraries_(new Libraries),
       unchecked_functions_(&gJniInvokeInterface),
-      weak_globals_lock_("JNI weak global reference table lock"),
+      weak_globals_lock_("JNI weak global reference table lock", kJniWeakGlobalsLock),
       weak_globals_(kWeakGlobalsInitial, kWeakGlobalsMax, kWeakGlobal),
       allow_new_weak_globals_(true),
       weak_globals_add_condition_("weak globals add condition", weak_globals_lock_) {
@@ -578,6 +578,13 @@
 
 mirror::Object* JavaVMExt::DecodeWeakGlobal(Thread* self, IndirectRef ref) {
   MutexLock mu(self, weak_globals_lock_);
+  return DecodeWeakGlobalLocked(self, ref);
+}
+
+mirror::Object* JavaVMExt::DecodeWeakGlobalLocked(Thread* self, IndirectRef ref) {
+  if (kDebugLocking) {
+    weak_globals_lock_.AssertHeld(self);
+  }
   while (UNLIKELY((!kUseReadBarrier && !allow_new_weak_globals_) ||
                   (kUseReadBarrier && !self->GetWeakRefAccessEnabled()))) {
     weak_globals_add_condition_.WaitHoldingLocks(self);
diff --git a/runtime/java_vm_ext.h b/runtime/java_vm_ext.h
index d70fc47..e80266f 100644
--- a/runtime/java_vm_ext.h
+++ b/runtime/java_vm_ext.h
@@ -133,7 +133,16 @@
       SHARED_REQUIRES(Locks::mutator_lock_) REQUIRES(!globals_lock_);
 
   mirror::Object* DecodeWeakGlobal(Thread* self, IndirectRef ref)
-      SHARED_REQUIRES(Locks::mutator_lock_) REQUIRES(!weak_globals_lock_);
+      SHARED_REQUIRES(Locks::mutator_lock_)
+      REQUIRES(!weak_globals_lock_);
+
+  mirror::Object* DecodeWeakGlobalLocked(Thread* self, IndirectRef ref)
+      SHARED_REQUIRES(Locks::mutator_lock_)
+      REQUIRES(weak_globals_lock_);
+
+  Mutex& WeakGlobalsLock() RETURN_CAPABILITY(weak_globals_lock_) {
+    return weak_globals_lock_;
+  }
 
   void UpdateWeakGlobal(Thread* self, IndirectRef ref, mirror::Object* result)
       SHARED_REQUIRES(Locks::mutator_lock_) REQUIRES(!weak_globals_lock_);
diff --git a/runtime/jni_internal.cc b/runtime/jni_internal.cc
index 6a716b5..6bc1829 100644
--- a/runtime/jni_internal.cc
+++ b/runtime/jni_internal.cc
@@ -1729,7 +1729,13 @@
     if (heap->IsMovableObject(s)) {
       StackHandleScope<1> hs(soa.Self());
       HandleWrapper<mirror::String> h(hs.NewHandleWrapper(&s));
-      heap->IncrementDisableMovingGC(soa.Self());
+      if (!kUseReadBarrier) {
+        heap->IncrementDisableMovingGC(soa.Self());
+      } else {
+        // For the CC collector, we only need to wait for the thread flip rather than the whole GC
+        // to occur thanks to the to-space invariant.
+        heap->IncrementDisableThreadFlip(soa.Self());
+      }
     }
     if (is_copy != nullptr) {
       *is_copy = JNI_FALSE;
@@ -1744,7 +1750,11 @@
     gc::Heap* heap = Runtime::Current()->GetHeap();
     mirror::String* s = soa.Decode<mirror::String*>(java_string);
     if (heap->IsMovableObject(s)) {
-      heap->DecrementDisableMovingGC(soa.Self());
+      if (!kUseReadBarrier) {
+        heap->DecrementDisableMovingGC(soa.Self());
+      } else {
+        heap->DecrementDisableThreadFlip(soa.Self());
+      }
     }
   }
 
@@ -1891,7 +1901,13 @@
     }
     gc::Heap* heap = Runtime::Current()->GetHeap();
     if (heap->IsMovableObject(array)) {
-      heap->IncrementDisableMovingGC(soa.Self());
+      if (!kUseReadBarrier) {
+        heap->IncrementDisableMovingGC(soa.Self());
+      } else {
+        // For the CC collector, we only need to wait for the thread flip rather than the whole GC
+        // to occur thanks to the to-space invariant.
+        heap->IncrementDisableThreadFlip(soa.Self());
+      }
       // Re-decode in case the object moved since IncrementDisableGC waits for GC to complete.
       array = soa.Decode<mirror::Array*>(java_array);
     }
@@ -2437,7 +2453,11 @@
         delete[] reinterpret_cast<uint64_t*>(elements);
       } else if (heap->IsMovableObject(array)) {
         // Non copy to a movable object must means that we had disabled the moving GC.
-        heap->DecrementDisableMovingGC(soa.Self());
+        if (!kUseReadBarrier) {
+          heap->DecrementDisableMovingGC(soa.Self());
+        } else {
+          heap->DecrementDisableThreadFlip(soa.Self());
+        }
       }
     }
   }
diff --git a/runtime/mirror/class.cc b/runtime/mirror/class.cc
index bed26b2..2ac44fc 100644
--- a/runtime/mirror/class.cc
+++ b/runtime/mirror/class.cc
@@ -44,7 +44,7 @@
       << java_lang_Class_.Read()
       << " " << java_lang_Class;
   CHECK(java_lang_Class != nullptr);
-  java_lang_Class->SetClassFlags(java_lang_Class->GetClassFlags() | mirror::kClassFlagClass);
+  java_lang_Class->SetClassFlags(mirror::kClassFlagClass);
   java_lang_Class_ = GcRoot<Class>(java_lang_Class);
 }
 
diff --git a/runtime/mirror/class.h b/runtime/mirror/class.h
index ef257ae..1420e5b 100644
--- a/runtime/mirror/class.h
+++ b/runtime/mirror/class.h
@@ -239,15 +239,15 @@
   }
 
   ALWAYS_INLINE void SetStringClass() SHARED_REQUIRES(Locks::mutator_lock_) {
-    SetClassFlags(GetClassFlags() | kClassFlagString | kClassFlagNoReferenceFields);
+    SetClassFlags(kClassFlagString | kClassFlagNoReferenceFields);
   }
 
   ALWAYS_INLINE bool IsClassLoaderClass() SHARED_REQUIRES(Locks::mutator_lock_) {
-    return (GetClassFlags() & kClassFlagClassLoader) != 0;
+    return GetClassFlags() == kClassFlagClassLoader;
   }
 
   ALWAYS_INLINE void SetClassLoaderClass() SHARED_REQUIRES(Locks::mutator_lock_) {
-    SetClassFlags(GetClassFlags() | kClassFlagClassLoader);
+    SetClassFlags(kClassFlagClassLoader);
   }
 
   // Returns true if the class is abstract.
@@ -282,22 +282,22 @@
 
   template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags>
   bool IsWeakReferenceClass() SHARED_REQUIRES(Locks::mutator_lock_) {
-    return (GetClassFlags<kVerifyFlags>() & kClassFlagWeakReference) != 0;
+    return GetClassFlags<kVerifyFlags>() == kClassFlagWeakReference;
   }
 
   template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags>
   bool IsSoftReferenceClass() SHARED_REQUIRES(Locks::mutator_lock_) {
-    return (GetClassFlags<kVerifyFlags>() & kClassFlagSoftReference) != 0;
+    return GetClassFlags<kVerifyFlags>() == kClassFlagSoftReference;
   }
 
   template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags>
   bool IsFinalizerReferenceClass() SHARED_REQUIRES(Locks::mutator_lock_) {
-    return (GetClassFlags<kVerifyFlags>() & kClassFlagFinalizerReference) != 0;
+    return GetClassFlags<kVerifyFlags>() == kClassFlagFinalizerReference;
   }
 
   template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags>
   bool IsPhantomReferenceClass() SHARED_REQUIRES(Locks::mutator_lock_) {
-    return (GetClassFlags<kVerifyFlags>() & kClassFlagPhantomReference) != 0;
+    return GetClassFlags<kVerifyFlags>() == kClassFlagPhantomReference;
   }
 
   // Can references of this type be assigned to by things of another type? For non-array types
@@ -870,8 +870,8 @@
   uint32_t NumInstanceFields() SHARED_REQUIRES(Locks::mutator_lock_);
   ArtField* GetInstanceField(uint32_t i) SHARED_REQUIRES(Locks::mutator_lock_);
 
-  // Returns the number of instance fields containing reference types not counting fields in the
-  // super class.
+  // Returns the number of instance fields containing reference types. Does not count fields in any
+  // super classes.
   uint32_t NumReferenceInstanceFields() SHARED_REQUIRES(Locks::mutator_lock_) {
     DCHECK(IsResolved() || IsErroneous());
     return GetField32(OFFSET_OF_OBJECT_MEMBER(Class, num_reference_instance_fields_));
diff --git a/runtime/mirror/class_flags.h b/runtime/mirror/class_flags.h
index 6c15639..eb2e2eb 100644
--- a/runtime/mirror/class_flags.h
+++ b/runtime/mirror/class_flags.h
@@ -22,27 +22,39 @@
 namespace art {
 namespace mirror {
 
-// Object types stored in class to help GC with faster object marking.
+// Normal instance with at least one ref field other than the class.
 static constexpr uint32_t kClassFlagNormal             = 0x00000000;
+
 // Only normal objects which have no reference fields, e.g. string or primitive array or normal
-// class instance.
+// class instance with no fields other than klass.
 static constexpr uint32_t kClassFlagNoReferenceFields  = 0x00000001;
+
+// Class is java.lang.String.class.
 static constexpr uint32_t kClassFlagString             = 0x00000004;
+
+// Class is an object array class.
 static constexpr uint32_t kClassFlagObjectArray        = 0x00000008;
+
+// Class is java.lang.Class.class.
 static constexpr uint32_t kClassFlagClass              = 0x00000010;
 
-// class is ClassLoader or one of its subclasses
+// Class is ClassLoader or one of its subclasses.
 static constexpr uint32_t kClassFlagClassLoader        = 0x00000020;
 
-// class is a soft/weak/phantom ref
+// Class is a soft/weak/phantom class.
 static constexpr uint32_t kClassFlagSoftReference      = 0x00000040;
-// class is a weak reference
+
+// Class is a weak reference class.
 static constexpr uint32_t kClassFlagWeakReference      = 0x00000080;
-// class is a finalizer reference
+
+// Class is a finalizer reference class.
 static constexpr uint32_t kClassFlagFinalizerReference = 0x00000100;
-// class is a phantom reference
+
+// Class is the phantom reference class.
 static constexpr uint32_t kClassFlagPhantomReference   = 0x00000200;
 
+// Combination of flags to figure out if the class is either the weak/soft/phantom/finalizer
+// reference class.
 static constexpr uint32_t kClassFlagReference =
     kClassFlagSoftReference |
     kClassFlagWeakReference |
diff --git a/runtime/mirror/object-inl.h b/runtime/mirror/object-inl.h
index 702a0f4..e35ddcc 100644
--- a/runtime/mirror/object-inl.h
+++ b/runtime/mirror/object-inl.h
@@ -1016,6 +1016,9 @@
     DCHECK(!klass->IsVariableSize());
     VisitInstanceFieldsReferences(klass, visitor);
     DCHECK(!klass->IsClassClass());
+    DCHECK(!klass->IsStringClass());
+    DCHECK(!klass->IsClassLoaderClass());
+    DCHECK(!klass->IsArrayClass());
   } else {
     if ((class_flags & kClassFlagNoReferenceFields) == 0) {
       DCHECK(!klass->IsStringClass());
diff --git a/runtime/mirror/string-inl.h b/runtime/mirror/string-inl.h
index 3a39f58..28a830d 100644
--- a/runtime/mirror/string-inl.h
+++ b/runtime/mirror/string-inl.h
@@ -18,8 +18,10 @@
 #define ART_RUNTIME_MIRROR_STRING_INL_H_
 
 #include "array.h"
+#include "base/bit_utils.h"
 #include "class.h"
 #include "gc/heap-inl.h"
+#include "globals.h"
 #include "intern_table.h"
 #include "runtime.h"
 #include "string.h"
@@ -142,27 +144,46 @@
 
 template<VerifyObjectFlags kVerifyFlags>
 inline size_t String::SizeOf() {
-  return sizeof(String) + (sizeof(uint16_t) * GetLength<kVerifyFlags>());
+  size_t size = sizeof(String) + (sizeof(uint16_t) * GetLength<kVerifyFlags>());
+  // String.equals() intrinsics assume zero-padding up to kObjectAlignment,
+  // so make sure the zero-padding is actually copied around if GC compaction
+  // chooses to copy only SizeOf() bytes.
+  // http://b/23528461
+  return RoundUp(size, kObjectAlignment);
 }
 
 template <bool kIsInstrumented, typename PreFenceVisitor>
 inline String* String::Alloc(Thread* self, int32_t utf16_length, gc::AllocatorType allocator_type,
                              const PreFenceVisitor& pre_fence_visitor) {
-  size_t header_size = sizeof(String);
-  size_t data_size = sizeof(uint16_t) * utf16_length;
+  constexpr size_t header_size = sizeof(String);
+  static_assert(sizeof(utf16_length) <= sizeof(size_t),
+                "static_cast<size_t>(utf16_length) must not lose bits.");
+  size_t length = static_cast<size_t>(utf16_length);
+  size_t data_size = sizeof(uint16_t) * length;
   size_t size = header_size + data_size;
+  // String.equals() intrinsics assume zero-padding up to kObjectAlignment,
+  // so make sure the allocator clears the padding as well.
+  // http://b/23528461
+  size_t alloc_size = RoundUp(size, kObjectAlignment);
   Class* string_class = GetJavaLangString();
 
   // Check for overflow and throw OutOfMemoryError if this was an unreasonable request.
-  if (UNLIKELY(size < data_size)) {
+  // Do this by comparing with the maximum length that will _not_ cause an overflow.
+  constexpr size_t overflow_length = (-header_size) / sizeof(uint16_t);  // Unsigned arithmetic.
+  constexpr size_t max_alloc_length = overflow_length - 1u;
+  static_assert(IsAligned<sizeof(uint16_t)>(kObjectAlignment),
+                "kObjectAlignment must be at least as big as Java char alignment");
+  constexpr size_t max_length = RoundDown(max_alloc_length, kObjectAlignment / sizeof(uint16_t));
+  if (UNLIKELY(length > max_length)) {
     self->ThrowOutOfMemoryError(StringPrintf("%s of length %d would overflow",
                                              PrettyDescriptor(string_class).c_str(),
                                              utf16_length).c_str());
     return nullptr;
   }
+
   gc::Heap* heap = Runtime::Current()->GetHeap();
   return down_cast<String*>(
-      heap->AllocObjectWithAllocator<kIsInstrumented, true>(self, string_class, size,
+      heap->AllocObjectWithAllocator<kIsInstrumented, true>(self, string_class, alloc_size,
                                                             allocator_type, pre_fence_visitor));
 }
 
diff --git a/runtime/monitor_test.cc b/runtime/monitor_test.cc
index e1173bb..69112b1 100644
--- a/runtime/monitor_test.cc
+++ b/runtime/monitor_test.cc
@@ -290,15 +290,13 @@
 static void CommonWaitSetup(MonitorTest* test, ClassLinker* class_linker, uint64_t create_sleep,
                             int64_t c_millis, bool c_expected, bool interrupt, uint64_t use_sleep,
                             int64_t u_millis, bool u_expected, const char* pool_name) {
+  Thread* const self = Thread::Current();
+  ScopedObjectAccess soa(self);
   // First create the object we lock. String is easiest.
-  StackHandleScope<3> hs(Thread::Current());
-  {
-    ScopedObjectAccess soa(Thread::Current());
-    test->object_ = hs.NewHandle(mirror::String::AllocFromModifiedUtf8(Thread::Current(),
-                                                                       "hello, world!"));
-    test->watchdog_object_ = hs.NewHandle(mirror::String::AllocFromModifiedUtf8(Thread::Current(),
-                                                                                "hello, world!"));
-  }
+  StackHandleScope<3> hs(soa.Self());
+  test->object_ = hs.NewHandle(mirror::String::AllocFromModifiedUtf8(self, "hello, world!"));
+  test->watchdog_object_ = hs.NewHandle(mirror::String::AllocFromModifiedUtf8(self,
+                                                                              "hello, world!"));
 
   // Create the barrier used to synchronize.
   test->barrier_ = std::unique_ptr<Barrier>(new Barrier(2));
@@ -308,23 +306,17 @@
   // Fill the heap.
   std::unique_ptr<StackHandleScope<kMaxHandles>> hsp;
   std::vector<MutableHandle<mirror::Object>> handles;
-  {
-    Thread* self = Thread::Current();
-    ScopedObjectAccess soa(self);
 
-    // Our job: Fill the heap, then try Wait.
-    FillHeap(self, class_linker, &hsp, &handles);
+  // Our job: Fill the heap, then try Wait.
+  FillHeap(soa.Self(), class_linker, &hsp, &handles);
 
-    // Now release everything.
-    auto it = handles.begin();
-    auto end = handles.end();
+  // Now release everything.
+  for (MutableHandle<mirror::Object>& h : handles) {
+    h.Assign(nullptr);
+  }
 
-    for ( ; it != end; ++it) {
-      it->Assign(nullptr);
-    }
-  }  // Need to drop the mutator lock to allow barriers.
-
-  Thread* self = Thread::Current();
+  // Need to drop the mutator lock to allow barriers.
+  soa.Self()->TransitionFromRunnableToSuspended(kNative);
   ThreadPool thread_pool(pool_name, 3);
   thread_pool.AddTask(self, new CreateTask(test, create_sleep, c_millis, c_expected));
   if (interrupt) {
@@ -336,19 +328,19 @@
   thread_pool.StartWorkers(self);
 
   // Wait on completion barrier.
-  test->complete_barrier_->Wait(Thread::Current());
+  test->complete_barrier_->Wait(self);
   test->completed_ = true;
 
   // Wake the watchdog.
   {
-    ScopedObjectAccess soa(Thread::Current());
-
+    ScopedObjectAccess soa2(self);
     test->watchdog_object_.Get()->MonitorEnter(self);     // Lock the object.
     test->watchdog_object_.Get()->NotifyAll(self);        // Wake up waiting parties.
     test->watchdog_object_.Get()->MonitorExit(self);      // Release the lock.
   }
 
   thread_pool.StopWorkers(self);
+  soa.Self()->TransitionFromSuspendedToRunnable();
 }
 
 
diff --git a/runtime/native/dalvik_system_DexFile.cc b/runtime/native/dalvik_system_DexFile.cc
index 4f97d20..3b84bfa 100644
--- a/runtime/native/dalvik_system_DexFile.cc
+++ b/runtime/native/dalvik_system_DexFile.cc
@@ -171,7 +171,7 @@
     if (array == nullptr) {
       ScopedObjectAccess soa(env);
       for (auto& dex_file : dex_files) {
-        if (Runtime::Current()->GetClassLinker()->IsDexFileRegistered(*dex_file)) {
+        if (linker->FindDexCache(soa.Self(), *dex_file, true) != nullptr) {
           dex_file.release();
         }
       }
@@ -208,8 +208,9 @@
   //
   // TODO: The Runtime should support unloading of classes and freeing of the
   // dex files for those unloaded classes rather than leaking dex files here.
-  for (auto& dex_file : *dex_files) {
-    if (!Runtime::Current()->GetClassLinker()->IsDexFileRegistered(*dex_file)) {
+  ClassLinker* const class_linker = Runtime::Current()->GetClassLinker();
+  for (const DexFile* dex_file : *dex_files) {
+    if (class_linker->FindDexCache(soa.Self(), *dex_file, true) == nullptr) {
       delete dex_file;
     }
   }
diff --git a/runtime/native/dalvik_system_VMRuntime.cc b/runtime/native/dalvik_system_VMRuntime.cc
index 9ea339a..4f95723 100644
--- a/runtime/native/dalvik_system_VMRuntime.cc
+++ b/runtime/native/dalvik_system_VMRuntime.cc
@@ -425,14 +425,17 @@
 static void PreloadDexCachesStatsFilled(DexCacheStats* filled)
     SHARED_REQUIRES(Locks::mutator_lock_) {
   if (!kPreloadDexCachesCollectStats) {
-      return;
+    return;
   }
-  ClassLinker* linker = Runtime::Current()->GetClassLinker();
-  const std::vector<const DexFile*>& boot_class_path = linker->GetBootClassPath();
-  for (size_t i = 0; i< boot_class_path.size(); i++) {
-    const DexFile* dex_file = boot_class_path[i];
+  ClassLinker* const class_linker = Runtime::Current()->GetClassLinker();
+  Thread* const self = Thread::Current();
+  for (const DexFile* dex_file : class_linker->GetBootClassPath()) {
     CHECK(dex_file != nullptr);
-    mirror::DexCache* dex_cache = linker->FindDexCache(*dex_file);
+    mirror::DexCache* const dex_cache = class_linker->FindDexCache(self, *dex_file, true);
+    // If dex cache was deallocated, just continue.
+    if (dex_cache == nullptr) {
+      continue;
+    }
     for (size_t j = 0; j < dex_cache->NumStrings(); j++) {
       mirror::String* string = dex_cache->GetResolvedString(j);
       if (string != nullptr) {
@@ -446,7 +449,7 @@
       }
     }
     for (size_t j = 0; j < dex_cache->NumResolvedFields(); j++) {
-      ArtField* field = linker->GetResolvedField(j, dex_cache);
+      ArtField* field = class_linker->GetResolvedField(j, dex_cache);
       if (field != nullptr) {
         filled->num_fields++;
       }
@@ -490,11 +493,11 @@
   }
 
   const std::vector<const DexFile*>& boot_class_path = linker->GetBootClassPath();
-  for (size_t i = 0; i< boot_class_path.size(); i++) {
+  for (size_t i = 0; i < boot_class_path.size(); i++) {
     const DexFile* dex_file = boot_class_path[i];
     CHECK(dex_file != nullptr);
     StackHandleScope<1> hs(soa.Self());
-    Handle<mirror::DexCache> dex_cache(hs.NewHandle(linker->FindDexCache(*dex_file)));
+    Handle<mirror::DexCache> dex_cache(hs.NewHandle(linker->RegisterDexFile(*dex_file)));
 
     if (kPreloadDexCachesStrings) {
       for (size_t j = 0; j < dex_cache->NumStrings(); j++) {
diff --git a/runtime/native/java_lang_Thread.cc b/runtime/native/java_lang_Thread.cc
index c76f6ee..c75ff78 100644
--- a/runtime/native/java_lang_Thread.cc
+++ b/runtime/native/java_lang_Thread.cc
@@ -90,6 +90,7 @@
     case kWaitingForMethodTracingStart:   return kJavaWaiting;
     case kWaitingForVisitObjects:         return kJavaWaiting;
     case kWaitingWeakGcRootRead:          return kJavaWaiting;
+    case kWaitingForGcThreadFlip:         return kJavaWaiting;
     case kSuspended:                      return kJavaRunnable;
     // Don't add a 'default' here so the compiler can spot incompatible enum changes.
   }
diff --git a/runtime/runtime-inl.h b/runtime/runtime-inl.h
index 380e72b..bfa8c54 100644
--- a/runtime/runtime-inl.h
+++ b/runtime/runtime-inl.h
@@ -20,6 +20,7 @@
 #include "runtime.h"
 
 #include "art_method.h"
+#include "class_linker.h"
 #include "read_barrier-inl.h"
 
 namespace art {
diff --git a/runtime/runtime.cc b/runtime/runtime.cc
index 1912314..a9dc16d 100644
--- a/runtime/runtime.cc
+++ b/runtime/runtime.cc
@@ -235,6 +235,9 @@
     self->GetJniEnv()->CallStaticVoidMethod(WellKnownClasses::java_lang_Daemons,
                                             WellKnownClasses::java_lang_Daemons_stop);
   }
+
+  Trace::Shutdown();
+
   if (attach_shutdown_thread) {
     DetachCurrentThread();
     self = nullptr;
@@ -245,8 +248,6 @@
     BackgroundMethodSamplingProfiler::Shutdown();
   }
 
-  Trace::Shutdown();
-
   // Make sure to let the GC complete if it is running.
   heap_->WaitForGcToComplete(gc::kGcCauseBackground, self);
   heap_->DeleteThreadPool();
@@ -791,6 +792,12 @@
   return failure_count;
 }
 
+void Runtime::SetSentinel(mirror::Object* sentinel) {
+  CHECK(sentinel_.Read() == nullptr);
+  CHECK(sentinel != nullptr);
+  sentinel_ = GcRoot<mirror::Object>(sentinel);
+}
+
 bool Runtime::Init(const RuntimeOptions& raw_options, bool ignore_unrecognized) {
   ATRACE_BEGIN("Runtime::Init");
   CHECK_EQ(sysconf(_SC_PAGE_SIZE), kPageSize);
@@ -1054,10 +1061,6 @@
 
   CHECK(class_linker_ != nullptr);
 
-  // Initialize the special sentinel_ value early.
-  sentinel_ = GcRoot<mirror::Object>(class_linker_->AllocObject(self));
-  CHECK(sentinel_.Read() != nullptr);
-
   verifier::MethodVerifier::Init();
 
   if (runtime_options.Exists(Opt::MethodTrace)) {
diff --git a/runtime/runtime.h b/runtime/runtime.h
index 4577b75..bd21db1 100644
--- a/runtime/runtime.h
+++ b/runtime/runtime.h
@@ -568,6 +568,9 @@
     return fingerprint_;
   }
 
+  // Called from class linker.
+  void SetSentinel(mirror::Object* sentinel) SHARED_REQUIRES(Locks::mutator_lock_);
+
  private:
   static void InitPlatformSignalHandlers();
 
diff --git a/runtime/thread_state.h b/runtime/thread_state.h
index a11d213..8f2f70f 100644
--- a/runtime/thread_state.h
+++ b/runtime/thread_state.h
@@ -44,6 +44,7 @@
   kWaitingForVisitObjects,          // WAITING        TS_WAIT      waiting for visiting objects
   kWaitingForGetObjectsAllocated,   // WAITING        TS_WAIT      waiting for getting the number of allocated objects
   kWaitingWeakGcRootRead,           // WAITING        TS_WAIT      waiting on the GC to read a weak root
+  kWaitingForGcThreadFlip,          // WAITING        TS_WAIT      waiting on the GC thread flip (CC collector) to finish
   kStarting,                        // NEW            TS_WAIT      native thread started, not yet ready to run managed code
   kNative,                          // RUNNABLE       TS_RUNNING   running in a JNI native method
   kSuspended,                       // RUNNABLE       TS_RUNNING   suspended by GC or debugger
diff --git a/runtime/trace.cc b/runtime/trace.cc
index 4393430..4ab5c0e 100644
--- a/runtime/trace.cc
+++ b/runtime/trace.cc
@@ -638,9 +638,11 @@
     const std::map<const DexFile*, DexIndexBitSet*>& seen_methods,
     std::set<ArtMethod*>* visited_methods) SHARED_REQUIRES(Locks::mutator_lock_) {
   ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
+  Thread* const self = Thread::Current();
   for (auto& e : seen_methods) {
     DexIndexBitSet* bit_set = e.second;
-    mirror::DexCache* dex_cache = class_linker->FindDexCache(*e.first);
+    // TODO: Visit trace methods as roots.
+    mirror::DexCache* dex_cache = class_linker->FindDexCache(self, *e.first, false);
     for (uint32_t i = 0; i < bit_set->size(); ++i) {
       if ((*bit_set)[i]) {
         visited_methods->insert(dex_cache->GetResolvedMethod(i, sizeof(void*)));
diff --git a/runtime/verifier/method_verifier.cc b/runtime/verifier/method_verifier.cc
index efefa8b..4f921bd 100644
--- a/runtime/verifier/method_verifier.cc
+++ b/runtime/verifier/method_verifier.cc
@@ -416,6 +416,7 @@
       have_any_pending_runtime_throw_failure_(false),
       new_instance_count_(0),
       monitor_enter_count_(0),
+      encountered_failure_types_(0),
       can_load_classes_(can_load_classes),
       allow_soft_failures_(allow_soft_failures),
       need_precise_constants_(need_precise_constants),
@@ -587,6 +588,9 @@
 }
 
 std::ostream& MethodVerifier::Fail(VerifyError error) {
+  // Mark the error type as encountered.
+  encountered_failure_types_ |= static_cast<uint32_t>(error);
+
   switch (error) {
     case VERIFY_ERROR_NO_CLASS:
     case VERIFY_ERROR_NO_FIELD:
@@ -597,6 +601,7 @@
     case VERIFY_ERROR_INSTANTIATION:
     case VERIFY_ERROR_CLASS_CHANGE:
     case VERIFY_ERROR_FORCE_INTERPRETER:
+    case VERIFY_ERROR_LOCKING:
       if (Runtime::Current()->IsAotCompiler() || !can_load_classes_) {
         // If we're optimistically running verification at compile time, turn NO_xxx, ACCESS_xxx,
         // class change and instantiation errors into soft verification errors so that we re-verify
@@ -627,12 +632,14 @@
         }
       }
       break;
+
       // Indication that verification should be retried at runtime.
     case VERIFY_ERROR_BAD_CLASS_SOFT:
       if (!allow_soft_failures_) {
         have_pending_hard_failure_ = true;
       }
       break;
+
       // Hard verification failures at compile time will still fail at runtime, so the class is
       // marked as rejected to prevent it from being compiled.
     case VERIFY_ERROR_BAD_CLASS_HARD: {
@@ -1653,6 +1660,33 @@
   return DexFile::kDexNoIndex;
 }
 
+// Setup a register line for the given return instruction.
+static void AdjustReturnLine(MethodVerifier* verifier,
+                             const Instruction* ret_inst,
+                             RegisterLine* line) {
+  Instruction::Code opcode = ret_inst->Opcode();
+
+  switch (opcode) {
+    case Instruction::RETURN_VOID:
+    case Instruction::RETURN_VOID_NO_BARRIER:
+      SafelyMarkAllRegistersAsConflicts(verifier, line);
+      break;
+
+    case Instruction::RETURN:
+    case Instruction::RETURN_OBJECT:
+      line->MarkAllRegistersAsConflictsExcept(verifier, ret_inst->VRegA_11x());
+      break;
+
+    case Instruction::RETURN_WIDE:
+      line->MarkAllRegistersAsConflictsExceptWide(verifier, ret_inst->VRegA_11x());
+      break;
+
+    default:
+      LOG(FATAL) << "Unknown return opcode " << opcode;
+      UNREACHABLE();
+  }
+}
+
 bool MethodVerifier::CodeFlowVerifyInstruction(uint32_t* start_guess) {
   // If we're doing FindLocksAtDexPc, check whether we're at the dex pc we care about.
   // We want the state _before_ the instruction, for the case where the dex pc we're
@@ -3074,10 +3108,9 @@
   } else if (have_pending_runtime_throw_failure_) {
     /* checking interpreter will throw, mark following code as unreachable */
     opcode_flags = Instruction::kThrow;
-    have_any_pending_runtime_throw_failure_ = true;
-    // Reset the pending_runtime_throw flag. The flag is a global to decouple Fail and is per
-    // instruction.
-    have_pending_runtime_throw_failure_ = false;
+    // Note: the flag must be reset as it is only global to decouple Fail and is semantically per
+    //       instruction. However, RETURN checking may throw LOCKING errors, so we clear at the
+    //       very end.
   }
   /*
    * If we didn't just set the result register, clear it out. This ensures that you can only use
@@ -3246,16 +3279,7 @@
     if (insn_flags_[next_insn_idx].IsReturn()) {
       // For returns we only care about the operand to the return, all other registers are dead.
       const Instruction* ret_inst = Instruction::At(code_item_->insns_ + next_insn_idx);
-      Instruction::Code opcode = ret_inst->Opcode();
-      if (opcode == Instruction::RETURN_VOID || opcode == Instruction::RETURN_VOID_NO_BARRIER) {
-        SafelyMarkAllRegistersAsConflicts(this, work_line_.get());
-      } else {
-        if (opcode == Instruction::RETURN_WIDE) {
-          work_line_->MarkAllRegistersAsConflictsExceptWide(this, ret_inst->VRegA_11x());
-        } else {
-          work_line_->MarkAllRegistersAsConflictsExcept(this, ret_inst->VRegA_11x());
-        }
-      }
+      AdjustReturnLine(this, ret_inst, work_line_.get());
     }
     RegisterLine* next_line = reg_table_.GetLine(next_insn_idx);
     if (next_line != nullptr) {
@@ -3276,9 +3300,7 @@
 
   /* If we're returning from the method, make sure monitor stack is empty. */
   if ((opcode_flags & Instruction::kReturn) != 0) {
-    if (!work_line_->VerifyMonitorStackEmpty(this)) {
-      return false;
-    }
+    work_line_->VerifyMonitorStackEmpty(this);
   }
 
   /*
@@ -3298,6 +3320,12 @@
   DCHECK_LT(*start_guess, code_item_->insns_size_in_code_units_);
   DCHECK(insn_flags_[*start_guess].IsOpcode());
 
+  if (have_pending_runtime_throw_failure_) {
+    have_any_pending_runtime_throw_failure_ = true;
+    // Reset the pending_runtime_throw flag now.
+    have_pending_runtime_throw_failure_ = false;
+  }
+
   return true;
 }  // NOLINT(readability/fn_size)
 
@@ -3379,6 +3407,7 @@
 ArtMethod* MethodVerifier::ResolveMethodAndCheckAccess(
     uint32_t dex_method_idx, MethodType method_type) {
   const DexFile::MethodId& method_id = dex_file_->GetMethodId(dex_method_idx);
+  // LOG(INFO) << dex_file_->NumTypeIds() << " " << dex_file_->NumClassDefs();
   const RegType& klass_type = ResolveClassAndCheckAccess(method_id.class_idx_);
   if (klass_type.IsConflict()) {
     std::string append(" in attempt to access method ");
@@ -4420,31 +4449,15 @@
      * there's nothing to "merge". Copy the registers over and mark it as changed. (This is the
      * only way a register can transition out of "unknown", so this is not just an optimization.)
      */
-    if (!insn_flags_[next_insn].IsReturn()) {
-      target_line->CopyFromLine(merge_line);
-    } else {
+    target_line->CopyFromLine(merge_line);
+    if (insn_flags_[next_insn].IsReturn()) {
       // Verify that the monitor stack is empty on return.
-      if (!merge_line->VerifyMonitorStackEmpty(this)) {
-        return false;
-      }
+      merge_line->VerifyMonitorStackEmpty(this);
+
       // For returns we only care about the operand to the return, all other registers are dead.
       // Initialize them as conflicts so they don't add to GC and deoptimization information.
       const Instruction* ret_inst = Instruction::At(code_item_->insns_ + next_insn);
-      Instruction::Code opcode = ret_inst->Opcode();
-      if (opcode == Instruction::RETURN_VOID || opcode == Instruction::RETURN_VOID_NO_BARRIER) {
-        // Explicitly copy the this-initialized flag from the merge-line, as we didn't copy its
-        // state. Must be done before SafelyMarkAllRegistersAsConflicts as that will do the
-        // super-constructor-call checking.
-        target_line->CopyThisInitialized(*merge_line);
-        SafelyMarkAllRegistersAsConflicts(this, target_line);
-      } else {
-        target_line->CopyFromLine(merge_line);
-        if (opcode == Instruction::RETURN_WIDE) {
-          target_line->MarkAllRegistersAsConflictsExceptWide(this, ret_inst->VRegA_11x());
-        } else {
-          target_line->MarkAllRegistersAsConflictsExcept(this, ret_inst->VRegA_11x());
-        }
-      }
+      AdjustReturnLine(this, ret_inst, target_line);
     }
   } else {
     std::unique_ptr<RegisterLine> copy(gDebugVerify ?
diff --git a/runtime/verifier/method_verifier.h b/runtime/verifier/method_verifier.h
index 21f8543..b57abf5 100644
--- a/runtime/verifier/method_verifier.h
+++ b/runtime/verifier/method_verifier.h
@@ -67,17 +67,17 @@
  * to be rewritten to fail at runtime.
  */
 enum VerifyError {
-  VERIFY_ERROR_BAD_CLASS_HARD,  // VerifyError; hard error that skips compilation.
-  VERIFY_ERROR_BAD_CLASS_SOFT,  // VerifyError; soft error that verifies again at runtime.
+  VERIFY_ERROR_BAD_CLASS_HARD = 1,        // VerifyError; hard error that skips compilation.
+  VERIFY_ERROR_BAD_CLASS_SOFT = 2,        // VerifyError; soft error that verifies again at runtime.
 
-  VERIFY_ERROR_NO_CLASS,        // NoClassDefFoundError.
-  VERIFY_ERROR_NO_FIELD,        // NoSuchFieldError.
-  VERIFY_ERROR_NO_METHOD,       // NoSuchMethodError.
-  VERIFY_ERROR_ACCESS_CLASS,    // IllegalAccessError.
-  VERIFY_ERROR_ACCESS_FIELD,    // IllegalAccessError.
-  VERIFY_ERROR_ACCESS_METHOD,   // IllegalAccessError.
-  VERIFY_ERROR_CLASS_CHANGE,    // IncompatibleClassChangeError.
-  VERIFY_ERROR_INSTANTIATION,   // InstantiationError.
+  VERIFY_ERROR_NO_CLASS = 4,              // NoClassDefFoundError.
+  VERIFY_ERROR_NO_FIELD = 8,              // NoSuchFieldError.
+  VERIFY_ERROR_NO_METHOD = 16,            // NoSuchMethodError.
+  VERIFY_ERROR_ACCESS_CLASS = 32,         // IllegalAccessError.
+  VERIFY_ERROR_ACCESS_FIELD = 64,         // IllegalAccessError.
+  VERIFY_ERROR_ACCESS_METHOD = 128,       // IllegalAccessError.
+  VERIFY_ERROR_CLASS_CHANGE = 256,        // IncompatibleClassChangeError.
+  VERIFY_ERROR_INSTANTIATION = 512,       // InstantiationError.
   // For opcodes that don't have complete verifier support (such as lambda opcodes),
   // we need a way to continue execution at runtime without attempting to re-verify
   // (since we know it will fail no matter what). Instead, run as the interpreter
@@ -85,25 +85,14 @@
   // on the fly.
   //
   // TODO: Once all new opcodes have implemented full verifier support, this can be removed.
-  VERIFY_ERROR_FORCE_INTERPRETER,  // Skip the verification phase at runtime;
-                                   // force the interpreter to do access checks.
-                                   // (sets a soft fail at compile time).
+  VERIFY_ERROR_FORCE_INTERPRETER = 1024,  // Skip the verification phase at runtime;
+                                          // force the interpreter to do access checks.
+                                          // (sets a soft fail at compile time).
+  VERIFY_ERROR_LOCKING = 2048,            // Could not guarantee balanced locking. This should be
+                                          // punted to the interpreter with access checks.
 };
 std::ostream& operator<<(std::ostream& os, const VerifyError& rhs);
 
-/*
- * Identifies the type of reference in the instruction that generated the verify error
- * (e.g. VERIFY_ERROR_ACCESS_CLASS could come from a method, field, or class reference).
- *
- * This must fit in two bits.
- */
-enum VerifyErrorRefType {
-  VERIFY_ERROR_REF_CLASS  = 0,
-  VERIFY_ERROR_REF_FIELD  = 1,
-  VERIFY_ERROR_REF_METHOD = 2,
-};
-const int kVerifyErrorRefTypeShift = 6;
-
 // We don't need to store the register data for many instructions, because we either only need
 // it at branch points (for verification) or GC points and branches (for verification +
 // type-precise register analysis).
@@ -291,6 +280,10 @@
     return string_init_pc_reg_map_;
   }
 
+  uint32_t GetEncounteredFailureTypes() {
+    return encountered_failure_types_;
+  }
+
  private:
   // Private constructor for dumping.
   MethodVerifier(Thread* self, const DexFile* dex_file, Handle<mirror::DexCache> dex_cache,
@@ -753,6 +746,9 @@
   size_t new_instance_count_;
   size_t monitor_enter_count_;
 
+  // Bitset of the encountered failure types. Bits are according to the values in VerifyError.
+  uint32_t encountered_failure_types_;
+
   const bool can_load_classes_;
 
   // Converts soft failures to hard failures when false. Only false when the compiler isn't
diff --git a/runtime/verifier/register_line-inl.h b/runtime/verifier/register_line-inl.h
index bee5834..1df2428 100644
--- a/runtime/verifier/register_line-inl.h
+++ b/runtime/verifier/register_line-inl.h
@@ -25,6 +25,10 @@
 namespace art {
 namespace verifier {
 
+// Should we dump a warning on failures to verify balanced locking? That would be an indication to
+// developers that their code will be slow.
+static constexpr bool kDumpLockFailures = true;
+
 inline const RegType& RegisterLine::GetRegisterType(MethodVerifier* verifier, uint32_t vsrc) const {
   // The register index was validated during the static pass, so we don't need to check it here.
   DCHECK_LT(vsrc, num_regs_);
@@ -167,12 +171,14 @@
   return true;
 }
 
-inline bool RegisterLine::VerifyMonitorStackEmpty(MethodVerifier* verifier) const {
+inline void RegisterLine::VerifyMonitorStackEmpty(MethodVerifier* verifier) const {
   if (MonitorStackDepth() != 0) {
-    verifier->Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "expected empty monitor stack";
-    return false;
-  } else {
-    return true;
+    verifier->Fail(VERIFY_ERROR_LOCKING);
+    if (kDumpLockFailures) {
+      LOG(WARNING) << "expected empty monitor stack in "
+                   << PrettyMethod(verifier->GetMethodReference().dex_method_index,
+                                   *verifier->GetMethodReference().dex_file);
+    }
   }
 }
 
diff --git a/runtime/verifier/register_line.cc b/runtime/verifier/register_line.cc
index bb6df76..33c90e3 100644
--- a/runtime/verifier/register_line.cc
+++ b/runtime/verifier/register_line.cc
@@ -344,14 +344,22 @@
     verifier->Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "monitor-enter on non-object ("
         << reg_type << ")";
   } else if (monitors_.size() >= 32) {
-    verifier->Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "monitor-enter stack overflow: "
-        << monitors_.size();
+    verifier->Fail(VERIFY_ERROR_LOCKING);
+    if (kDumpLockFailures) {
+      LOG(WARNING) << "monitor-enter stack overflow while verifying "
+                   << PrettyMethod(verifier->GetMethodReference().dex_method_index,
+                                   *verifier->GetMethodReference().dex_file);
+    }
   } else {
     if (SetRegToLockDepth(reg_idx, monitors_.size())) {
       monitors_.push_back(insn_idx);
     } else {
-      verifier->Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "unexpected monitor-enter on register v" <<
-          reg_idx;
+      verifier->Fail(VERIFY_ERROR_LOCKING);
+      if (kDumpLockFailures) {
+        LOG(WARNING) << "unexpected monitor-enter on register v" <<  reg_idx << " in "
+                     << PrettyMethod(verifier->GetMethodReference().dex_method_index,
+                                     *verifier->GetMethodReference().dex_file);
+      }
     }
   }
 }
@@ -361,16 +369,21 @@
   if (!reg_type.IsReferenceTypes()) {
     verifier->Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "monitor-exit on non-object (" << reg_type << ")";
   } else if (monitors_.empty()) {
-    verifier->Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "monitor-exit stack underflow";
+    verifier->Fail(VERIFY_ERROR_LOCKING);
+    if (kDumpLockFailures) {
+      LOG(WARNING) << "monitor-exit stack underflow while verifying "
+                   << PrettyMethod(verifier->GetMethodReference().dex_method_index,
+                                   *verifier->GetMethodReference().dex_file);
+    }
   } else {
     monitors_.pop_back();
     if (!IsSetLockDepth(reg_idx, monitors_.size())) {
-      // Bug 3215458: Locks and unlocks are on objects, if that object is a literal then before
-      // format "036" the constant collector may create unlocks on the same object but referenced
-      // via different registers.
-      ((verifier->DexFileVersion() >= 36) ? verifier->Fail(VERIFY_ERROR_BAD_CLASS_SOFT)
-                                          : verifier->LogVerifyInfo())
-            << "monitor-exit not unlocking the top of the monitor stack";
+      verifier->Fail(VERIFY_ERROR_LOCKING);
+      if (kDumpLockFailures) {
+        LOG(WARNING) << "monitor-exit not unlocking the top of the monitor stack while verifying "
+                     << PrettyMethod(verifier->GetMethodReference().dex_method_index,
+                                     *verifier->GetMethodReference().dex_file);
+      }
     } else {
       // Record the register was unlocked
       ClearRegToLockDepth(reg_idx, monitors_.size());
@@ -392,8 +405,13 @@
   }
   if (monitors_.size() > 0 || incoming_line->monitors_.size() > 0) {
     if (monitors_.size() != incoming_line->monitors_.size()) {
-      LOG(WARNING) << "mismatched stack depths (depth=" << MonitorStackDepth()
-                     << ", incoming depth=" << incoming_line->MonitorStackDepth() << ")";
+      verifier->Fail(VERIFY_ERROR_LOCKING);
+      if (kDumpLockFailures) {
+        LOG(WARNING) << "mismatched stack depths (depth=" << MonitorStackDepth()
+                     << ", incoming depth=" << incoming_line->MonitorStackDepth() << ") in "
+                     << PrettyMethod(verifier->GetMethodReference().dex_method_index,
+                                     *verifier->GetMethodReference().dex_file);
+      }
     } else if (reg_to_lock_depths_ != incoming_line->reg_to_lock_depths_) {
       for (uint32_t idx = 0; idx < num_regs_; idx++) {
         size_t depths = reg_to_lock_depths_.count(idx);
@@ -402,14 +420,35 @@
           if (depths == 0 || incoming_depths == 0) {
             reg_to_lock_depths_.erase(idx);
           } else {
-            LOG(WARNING) << "mismatched stack depths for register v" << idx
-                << ": " << depths  << " != " << incoming_depths;
+            verifier->Fail(VERIFY_ERROR_LOCKING);
+            if (kDumpLockFailures) {
+              LOG(WARNING) << "mismatched stack depths for register v" << idx
+                           << ": " << depths  << " != " << incoming_depths << " in "
+                           << PrettyMethod(verifier->GetMethodReference().dex_method_index,
+                                           *verifier->GetMethodReference().dex_file);
+            }
+            break;
+          }
+        } else if (depths > 0) {
+          // Check whether they're actually the same levels.
+          uint32_t locked_levels = reg_to_lock_depths_.find(idx)->second;
+          uint32_t incoming_locked_levels = incoming_line->reg_to_lock_depths_.find(idx)->second;
+          if (locked_levels != incoming_locked_levels) {
+            verifier->Fail(VERIFY_ERROR_LOCKING);
+            if (kDumpLockFailures) {
+              LOG(WARNING) << "mismatched lock levels for register v" << idx << ": "
+                  << std::hex << locked_levels << std::dec  << " != "
+                  << std::hex << incoming_locked_levels << std::dec << " in "
+                  << PrettyMethod(verifier->GetMethodReference().dex_method_index,
+                                  *verifier->GetMethodReference().dex_file);
+            }
             break;
           }
         }
       }
     }
   }
+
   // Check whether "this" was initialized in both paths.
   if (this_initialized_ && !incoming_line->this_initialized_) {
     this_initialized_ = false;
diff --git a/runtime/verifier/register_line.h b/runtime/verifier/register_line.h
index 41f1e28..46db1c6 100644
--- a/runtime/verifier/register_line.h
+++ b/runtime/verifier/register_line.h
@@ -185,7 +185,9 @@
   // Compare two register lines. Returns 0 if they match.
   // Using this for a sort is unwise, since the value can change based on machine endianness.
   int CompareLine(const RegisterLine* line2) const {
-    DCHECK(monitors_ == line2->monitors_);
+    if (monitors_ != line2->monitors_) {
+      return 1;
+    }
     // TODO: DCHECK(reg_to_lock_depths_ == line2->reg_to_lock_depths_);
     return memcmp(&line_, &line2->line_, num_regs_ * sizeof(uint16_t));
   }
@@ -298,8 +300,8 @@
   }
 
   // We expect no monitors to be held at certain points, such a method returns. Verify the stack
-  // is empty, failing and returning false if not.
-  bool VerifyMonitorStackEmpty(MethodVerifier* verifier) const;
+  // is empty, queueing a LOCKING error else.
+  void VerifyMonitorStackEmpty(MethodVerifier* verifier) const;
 
   bool MergeRegisters(MethodVerifier* verifier, const RegisterLine* incoming_line)
       SHARED_REQUIRES(Locks::mutator_lock_);
diff --git a/test/002-sleep/src/Main.java b/test/002-sleep/src/Main.java
index c1a2d83..55032fd 100644
--- a/test/002-sleep/src/Main.java
+++ b/test/002-sleep/src/Main.java
@@ -2,8 +2,8 @@
     static public void main(String[] args) throws Exception {
         int millis = 1000;
 
-        if (args.length != 0) {
-            millis = Integer.parseInt(args[0]);
+        if (args.length > 1) {
+            millis = Integer.parseInt(args[1]);
         }
 
         System.out.println("Sleeping " + millis + " msec...");
diff --git a/test/004-JniTest/src/Main.java b/test/004-JniTest/src/Main.java
index 810dda0..dd88db0 100644
--- a/test/004-JniTest/src/Main.java
+++ b/test/004-JniTest/src/Main.java
@@ -20,7 +20,7 @@
 
 public class Main {
     public static void main(String[] args) {
-        System.loadLibrary("arttest");
+        System.loadLibrary(args[0]);
         testFindClassOnAttachedNativeThread();
         testFindFieldOnAttachedNativeThread();
         testReflectFieldGetFromAttachedNativeThreadNative();
diff --git a/test/004-ReferenceMap/src/Main.java b/test/004-ReferenceMap/src/Main.java
index f9a5498..dacd748 100644
--- a/test/004-ReferenceMap/src/Main.java
+++ b/test/004-ReferenceMap/src/Main.java
@@ -36,11 +36,8 @@
   }
   native int refmap(int x);
 
-  static {
-    System.loadLibrary("arttest");
-  }
-
   public static void main(String[] args) {
+    System.loadLibrary(args[0]);
     Main rm = new Main();
     rm.f();
   }
diff --git a/test/004-SignalTest/signaltest.cc b/test/004-SignalTest/signaltest.cc
index 34e331b..6dd6355 100644
--- a/test/004-SignalTest/signaltest.cc
+++ b/test/004-SignalTest/signaltest.cc
@@ -62,9 +62,12 @@
   struct ucontext *uc = reinterpret_cast<struct ucontext*>(context);
   struct sigcontext *sc = reinterpret_cast<struct sigcontext*>(&uc->uc_mcontext);
   sc->pc += 4;          // Skip instruction causing segv.
-#elif defined(__i386__) || defined(__x86_64__)
+#elif defined(__i386__)
   struct ucontext *uc = reinterpret_cast<struct ucontext*>(context);
   uc->CTX_EIP += 3;
+#elif defined(__x86_64__)
+  struct ucontext *uc = reinterpret_cast<struct ucontext*>(context);
+  uc->CTX_EIP += 2;
 #else
   UNUSED(context);
 #endif
@@ -97,9 +100,9 @@
   // On supported architectures we cause a real SEGV.
   *go_away_compiler = 'a';
 #elif defined(__x86_64__)
-  // Cause a SEGV using an instruction known to be 3 bytes long to account for hardcoded jump
+  // Cause a SEGV using an instruction known to be 2 bytes long to account for hardcoded jump
   // in the signal handler
-  asm volatile("movl $0, %%eax;" "movb $1, (%%al);" : : : "%eax");
+  asm volatile("movl $0, %%eax;" "movb %%ah, (%%rax);" : : : "%eax");
 #else
   // On other architectures we simulate SEGV.
   kill(getpid(), SIGSEGV);
diff --git a/test/004-SignalTest/src/Main.java b/test/004-SignalTest/src/Main.java
index 8b1f49b..6266918 100644
--- a/test/004-SignalTest/src/Main.java
+++ b/test/004-SignalTest/src/Main.java
@@ -24,8 +24,7 @@
     }
 
     public static void main(String[] args) {
-        System.loadLibrary("arttest");
-
+        System.loadLibrary(args[0]);
         System.out.println("init signal test");
         initSignalTest();
         try {
diff --git a/test/004-StackWalk/src/Main.java b/test/004-StackWalk/src/Main.java
index 9a1d0ab..883ce2c 100644
--- a/test/004-StackWalk/src/Main.java
+++ b/test/004-StackWalk/src/Main.java
@@ -87,11 +87,8 @@
 
   native int stackmap(int x);
 
-  static {
-    System.loadLibrary("arttest");
-  }
-
   public static void main(String[] args) throws Exception {
+    System.loadLibrary(args[0]);
     Main st = new Main();
     st.$noinline$f();
   }
diff --git a/test/004-ThreadStress/src/Main.java b/test/004-ThreadStress/src/Main.java
index d5b389f..4eeae2f 100644
--- a/test/004-ThreadStress/src/Main.java
+++ b/test/004-ThreadStress/src/Main.java
@@ -310,7 +310,8 @@
         boolean dumpMap = false;
 
         if (args != null) {
-            for (int i = 0; i < args.length; i++) {
+            // args[0] is libarttest
+            for (int i = 1; i < args.length; i++) {
                 if (args[i].equals("-n")) {
                     i++;
                     numberOfThreads = Integer.parseInt(args[i]);
diff --git a/test/004-UnsafeTest/src/Main.java b/test/004-UnsafeTest/src/Main.java
index 818f5d9..c93db50 100644
--- a/test/004-UnsafeTest/src/Main.java
+++ b/test/004-UnsafeTest/src/Main.java
@@ -18,10 +18,6 @@
 import sun.misc.Unsafe;
 
 public class Main {
-  static {
-    System.loadLibrary("arttest");
-  }
-
   private static void check(int actual, int expected, String msg) {
     if (actual != expected) {
       System.out.println(msg + " : " + actual + " != " + expected);
@@ -51,6 +47,7 @@
   }
 
   public static void main(String[] args) throws Exception {
+    System.loadLibrary(args[0]);
     Unsafe unsafe = getUnsafe();
     check(unsafe.arrayBaseOffset(boolean[].class), vmArrayBaseOffset(boolean[].class),
         "Unsafe.arrayBaseOffset(boolean[])");
diff --git a/test/051-thread/src/Main.java b/test/051-thread/src/Main.java
index b81273e..2e26b22 100644
--- a/test/051-thread/src/Main.java
+++ b/test/051-thread/src/Main.java
@@ -20,11 +20,8 @@
  * Test some basic thread stuff.
  */
 public class Main {
-    static {
-        System.loadLibrary("arttest");
-    }
-
     public static void main(String[] args) throws Exception {
+        System.loadLibrary(args[0]);
         System.out.println("thread test starting");
         testThreadCapacity();
         testThreadDaemons();
diff --git a/test/082-inline-execute/src/Main.java b/test/082-inline-execute/src/Main.java
index bd606a6..08ccf0e 100644
--- a/test/082-inline-execute/src/Main.java
+++ b/test/082-inline-execute/src/Main.java
@@ -594,6 +594,54 @@
     Assert.assertEquals(Math.ceil(-2.5), -2.0d, 0.0);
     Assert.assertEquals(Math.ceil(-2.9), -2.0d, 0.0);
     Assert.assertEquals(Math.ceil(-3.0), -3.0d, 0.0);
+    // 2^52 - 1.5
+    Assert.assertEquals(Math.ceil(Double.longBitsToDouble(0x432FFFFFFFFFFFFDl)),
+                        Double.longBitsToDouble(0x432FFFFFFFFFFFFEl), 0.0);
+    // 2^52 - 0.5
+    Assert.assertEquals(Math.ceil(Double.longBitsToDouble(0x432FFFFFFFFFFFFFl)),
+                        Double.longBitsToDouble(0x4330000000000000l), 0.0);
+    // 2^52
+    Assert.assertEquals(Math.ceil(Double.longBitsToDouble(0x4330000000000000l)),
+                        Double.longBitsToDouble(0x4330000000000000l), 0.0);
+    // 2^53 - 1
+    Assert.assertEquals(Math.ceil(Double.longBitsToDouble(0x433FFFFFFFFFFFFFl)),
+                        Double.longBitsToDouble(0x433FFFFFFFFFFFFFl), 0.0);
+    // 2^53
+    Assert.assertEquals(Math.ceil(Double.longBitsToDouble(0x4340000000000000l)),
+                        Double.longBitsToDouble(0x4340000000000000l), 0.0);
+    // 2^63 - 2^10
+    Assert.assertEquals(Math.ceil(Double.longBitsToDouble(0x43DFFFFFFFFFFFFFl)),
+                        Double.longBitsToDouble(0x43DFFFFFFFFFFFFFl), 0.0);
+    // 2^63
+    Assert.assertEquals(Math.ceil(Double.longBitsToDouble(0x43E0000000000000l)),
+                        Double.longBitsToDouble(0x43E0000000000000l), 0.0);
+    // 2^64
+    Assert.assertEquals(Math.ceil(Double.longBitsToDouble(0x43F0000000000000l)),
+                        Double.longBitsToDouble(0x43F0000000000000l), 0.0);
+    // -(2^52 - 1.5)
+    Assert.assertEquals(Math.ceil(Double.longBitsToDouble(0xC32FFFFFFFFFFFFDl)),
+                        Double.longBitsToDouble(0xC32FFFFFFFFFFFFCl), 0.0);
+    // -(2^52 - 0.5)
+    Assert.assertEquals(Math.ceil(Double.longBitsToDouble(0xC32FFFFFFFFFFFFFl)),
+                        Double.longBitsToDouble(0xC32FFFFFFFFFFFFEl), 0.0);
+    // -2^52
+    Assert.assertEquals(Math.ceil(Double.longBitsToDouble(0xC330000000000000l)),
+                        Double.longBitsToDouble(0xC330000000000000l), 0.0);
+    // -(2^53 - 1)
+    Assert.assertEquals(Math.ceil(Double.longBitsToDouble(0xC33FFFFFFFFFFFFFl)),
+                        Double.longBitsToDouble(0xC33FFFFFFFFFFFFFl), 0.0);
+    // -2^53
+    Assert.assertEquals(Math.ceil(Double.longBitsToDouble(0xC340000000000000l)),
+                        Double.longBitsToDouble(0xC340000000000000l), 0.0);
+    // -(2^63 - 2^10)
+    Assert.assertEquals(Math.ceil(Double.longBitsToDouble(0xC3DFFFFFFFFFFFFFl)),
+                        Double.longBitsToDouble(0xC3DFFFFFFFFFFFFFl), 0.0);
+    // -2^63
+    Assert.assertEquals(Math.ceil(Double.longBitsToDouble(0xC3E0000000000000l)),
+                        Double.longBitsToDouble(0xC3E0000000000000l), 0.0);
+    // -2^64
+    Assert.assertEquals(Math.ceil(Double.longBitsToDouble(0xC3F0000000000000l)),
+                        Double.longBitsToDouble(0xC3F0000000000000l), 0.0);
     Assert.assertEquals(Math.ceil(Double.NaN), Double.NaN, 0.0);
     Assert.assertEquals(Math.ceil(Double.POSITIVE_INFINITY), Double.POSITIVE_INFINITY, 0.0);
     Assert.assertEquals(Math.ceil(Double.NEGATIVE_INFINITY), Double.NEGATIVE_INFINITY, 0.0);
@@ -613,6 +661,54 @@
     Assert.assertEquals(Math.floor(-2.5), -3.0d, 0.0);
     Assert.assertEquals(Math.floor(-2.9), -3.0d, 0.0);
     Assert.assertEquals(Math.floor(-3.0), -3.0d, 0.0);
+    // 2^52 - 1.5
+    Assert.assertEquals(Math.floor(Double.longBitsToDouble(0x432FFFFFFFFFFFFDl)),
+                        Double.longBitsToDouble(0x432FFFFFFFFFFFFCl), 0.0);
+    // 2^52 - 0.5
+    Assert.assertEquals(Math.floor(Double.longBitsToDouble(0x432FFFFFFFFFFFFFl)),
+                        Double.longBitsToDouble(0x432FFFFFFFFFFFFEl), 0.0);
+    // 2^52
+    Assert.assertEquals(Math.floor(Double.longBitsToDouble(0x4330000000000000l)),
+                        Double.longBitsToDouble(0x4330000000000000l), 0.0);
+    // 2^53 - 1
+    Assert.assertEquals(Math.floor(Double.longBitsToDouble(0x433FFFFFFFFFFFFFl)),
+                        Double.longBitsToDouble(0x433FFFFFFFFFFFFFl), 0.0);
+    // 2^53
+    Assert.assertEquals(Math.floor(Double.longBitsToDouble(0x4340000000000000l)),
+                        Double.longBitsToDouble(0x4340000000000000l), 0.0);
+    // 2^63 - 2^10
+    Assert.assertEquals(Math.floor(Double.longBitsToDouble(0x43DFFFFFFFFFFFFFl)),
+                        Double.longBitsToDouble(0x43DFFFFFFFFFFFFFl), 0.0);
+    // 2^63
+    Assert.assertEquals(Math.floor(Double.longBitsToDouble(0x43E0000000000000l)),
+                        Double.longBitsToDouble(0x43E0000000000000l), 0.0);
+    // 2^64
+    Assert.assertEquals(Math.floor(Double.longBitsToDouble(0x43F0000000000000l)),
+                        Double.longBitsToDouble(0x43F0000000000000l), 0.0);
+    // -(2^52 - 1.5)
+    Assert.assertEquals(Math.floor(Double.longBitsToDouble(0xC32FFFFFFFFFFFFDl)),
+                        Double.longBitsToDouble(0xC32FFFFFFFFFFFFEl), 0.0);
+    // -(2^52 - 0.5)
+    Assert.assertEquals(Math.floor(Double.longBitsToDouble(0xC32FFFFFFFFFFFFFl)),
+                        Double.longBitsToDouble(0xC330000000000000l), 0.0);
+    // -2^52
+    Assert.assertEquals(Math.floor(Double.longBitsToDouble(0xC330000000000000l)),
+                        Double.longBitsToDouble(0xC330000000000000l), 0.0);
+    // -(2^53 - 1)
+    Assert.assertEquals(Math.floor(Double.longBitsToDouble(0xC33FFFFFFFFFFFFFl)),
+                        Double.longBitsToDouble(0xC33FFFFFFFFFFFFFl), 0.0);
+    // -2^53
+    Assert.assertEquals(Math.floor(Double.longBitsToDouble(0xC340000000000000l)),
+                        Double.longBitsToDouble(0xC340000000000000l), 0.0);
+    // -(2^63 - 2^10)
+    Assert.assertEquals(Math.floor(Double.longBitsToDouble(0xC3DFFFFFFFFFFFFFl)),
+                        Double.longBitsToDouble(0xC3DFFFFFFFFFFFFFl), 0.0);
+    // -2^63
+    Assert.assertEquals(Math.floor(Double.longBitsToDouble(0xC3E0000000000000l)),
+                        Double.longBitsToDouble(0xC3E0000000000000l), 0.0);
+    // -2^64
+    Assert.assertEquals(Math.floor(Double.longBitsToDouble(0xC3F0000000000000l)),
+                        Double.longBitsToDouble(0xC3F0000000000000l), 0.0);
     Assert.assertEquals(Math.floor(Double.NaN), Double.NaN, 0.0);
     Assert.assertEquals(Math.floor(Double.POSITIVE_INFINITY), Double.POSITIVE_INFINITY, 0.0);
     Assert.assertEquals(Math.floor(Double.NEGATIVE_INFINITY), Double.NEGATIVE_INFINITY, 0.0);
@@ -632,6 +728,54 @@
     Assert.assertEquals(Math.rint(-2.5), -2.0d, 0.0);
     Assert.assertEquals(Math.rint(-2.9), -3.0d, 0.0);
     Assert.assertEquals(Math.rint(-3.0), -3.0d, 0.0);
+    // 2^52 - 1.5
+    Assert.assertEquals(Math.rint(Double.longBitsToDouble(0x432FFFFFFFFFFFFDl)),
+                        Double.longBitsToDouble(0x432FFFFFFFFFFFFCl), 0.0);
+    // 2^52 - 0.5
+    Assert.assertEquals(Math.rint(Double.longBitsToDouble(0x432FFFFFFFFFFFFFl)),
+                        Double.longBitsToDouble(0x4330000000000000l), 0.0);
+    // 2^52
+    Assert.assertEquals(Math.rint(Double.longBitsToDouble(0x4330000000000000l)),
+                        Double.longBitsToDouble(0x4330000000000000l), 0.0);
+    // 2^53 - 1
+    Assert.assertEquals(Math.rint(Double.longBitsToDouble(0x433FFFFFFFFFFFFFl)),
+                        Double.longBitsToDouble(0x433FFFFFFFFFFFFFl), 0.0);
+    // 2^53
+    Assert.assertEquals(Math.rint(Double.longBitsToDouble(0x4340000000000000l)),
+                        Double.longBitsToDouble(0x4340000000000000l), 0.0);
+    // 2^63 - 2^10
+    Assert.assertEquals(Math.rint(Double.longBitsToDouble(0x43DFFFFFFFFFFFFFl)),
+                        Double.longBitsToDouble(0x43DFFFFFFFFFFFFFl), 0.0);
+    // 2^63
+    Assert.assertEquals(Math.rint(Double.longBitsToDouble(0x43E0000000000000l)),
+                        Double.longBitsToDouble(0x43E0000000000000l), 0.0);
+    // 2^64
+    Assert.assertEquals(Math.rint(Double.longBitsToDouble(0x43F0000000000000l)),
+                        Double.longBitsToDouble(0x43F0000000000000l), 0.0);
+    // -(2^52 - 1.5)
+    Assert.assertEquals(Math.rint(Double.longBitsToDouble(0xC32FFFFFFFFFFFFDl)),
+                        Double.longBitsToDouble(0xC32FFFFFFFFFFFFCl), 0.0);
+    // -(2^52 - 0.5)
+    Assert.assertEquals(Math.rint(Double.longBitsToDouble(0xC32FFFFFFFFFFFFFl)),
+                        Double.longBitsToDouble(0xC330000000000000l), 0.0);
+    // -2^52
+    Assert.assertEquals(Math.rint(Double.longBitsToDouble(0xC330000000000000l)),
+                        Double.longBitsToDouble(0xC330000000000000l), 0.0);
+    // -(2^53 - 1)
+    Assert.assertEquals(Math.rint(Double.longBitsToDouble(0xC33FFFFFFFFFFFFFl)),
+                        Double.longBitsToDouble(0xC33FFFFFFFFFFFFFl), 0.0);
+    // -2^53
+    Assert.assertEquals(Math.rint(Double.longBitsToDouble(0xC340000000000000l)),
+                        Double.longBitsToDouble(0xC340000000000000l), 0.0);
+    // -(2^63 - 2^10)
+    Assert.assertEquals(Math.rint(Double.longBitsToDouble(0xC3DFFFFFFFFFFFFFl)),
+                        Double.longBitsToDouble(0xC3DFFFFFFFFFFFFFl), 0.0);
+    // -2^63
+    Assert.assertEquals(Math.rint(Double.longBitsToDouble(0xC3E0000000000000l)),
+                        Double.longBitsToDouble(0xC3E0000000000000l), 0.0);
+    // -2^64
+    Assert.assertEquals(Math.rint(Double.longBitsToDouble(0xC3F0000000000000l)),
+                        Double.longBitsToDouble(0xC3F0000000000000l), 0.0);
     Assert.assertEquals(Math.rint(Double.NaN), Double.NaN, 0.0);
     Assert.assertEquals(Math.rint(Double.POSITIVE_INFINITY), Double.POSITIVE_INFINITY, 0.0);
     Assert.assertEquals(Math.rint(Double.NEGATIVE_INFINITY), Double.NEGATIVE_INFINITY, 0.0);
diff --git a/test/088-monitor-verification/expected.txt b/test/088-monitor-verification/expected.txt
index 07f5b0b..13b8c73 100644
--- a/test/088-monitor-verification/expected.txt
+++ b/test/088-monitor-verification/expected.txt
@@ -1,7 +1,12 @@
 recursiveSync ok
 nestedMayThrow ok
 constantLock ok
-excessiveNesting ok
 notNested ok
 twoPath ok
 triplet ok
+OK
+TooDeep
+NotStructuredOverUnlock
+NotStructuredUnderUnlock
+UnbalancedJoin
+UnbalancedStraight
diff --git a/test/088-monitor-verification/smali/NotStructuredOverUnlock.smali b/test/088-monitor-verification/smali/NotStructuredOverUnlock.smali
new file mode 100644
index 0000000..aa0c2d5
--- /dev/null
+++ b/test/088-monitor-verification/smali/NotStructuredOverUnlock.smali
@@ -0,0 +1,21 @@
+.class public LNotStructuredOverUnlock;
+
+.super Ljava/lang/Object;
+
+.method public static run(Ljava/lang/Object;)V
+   .registers 3
+
+   invoke-static {}, LMain;->assertCallerIsInterpreted()V
+
+   # Lock twice, but unlock thrice.
+
+   monitor-enter v2        #  1
+   monitor-enter v2        #  2
+
+   monitor-exit v2         #  1
+   monitor-exit v2         #  2
+   monitor-exit v2         #  3
+
+   return-void
+
+.end method
diff --git a/test/088-monitor-verification/smali/NotStructuredUnderUnlock.smali b/test/088-monitor-verification/smali/NotStructuredUnderUnlock.smali
new file mode 100644
index 0000000..2c31fda
--- /dev/null
+++ b/test/088-monitor-verification/smali/NotStructuredUnderUnlock.smali
@@ -0,0 +1,21 @@
+.class public LNotStructuredUnderUnlock;
+
+.super Ljava/lang/Object;
+
+.method public static run(Ljava/lang/Object;)V
+   .registers 3
+
+   invoke-static {}, LMain;->assertCallerIsInterpreted()V
+
+   # Lock thrice, but only unlock twice.
+
+   monitor-enter v2        #  1
+   monitor-enter v2        #  2
+   monitor-enter v2        #  3
+
+   monitor-exit v2         #  1
+   monitor-exit v2         #  2
+
+   return-void
+
+.end method
diff --git a/test/088-monitor-verification/smali/OK.smali b/test/088-monitor-verification/smali/OK.smali
new file mode 100644
index 0000000..596798d
--- /dev/null
+++ b/test/088-monitor-verification/smali/OK.smali
@@ -0,0 +1,68 @@
+.class public LOK;
+
+.super Ljava/lang/Object;
+
+.method public static run(Ljava/lang/Object;Ljava/lang/Object;)V
+   .registers 3
+
+   invoke-static {v1, v2}, LOK;->runNoMonitors(Ljava/lang/Object;Ljava/lang/Object;)V
+
+   invoke-static {v1, v2}, LOK;->runStraightLine(Ljava/lang/Object;Ljava/lang/Object;)V
+
+   invoke-static {v1, v2}, LOK;->runBalancedJoin(Ljava/lang/Object;Ljava/lang/Object;)V
+
+   return-void
+
+.end method
+
+
+
+.method public static runNoMonitors(Ljava/lang/Object;Ljava/lang/Object;)V
+   .registers 3
+
+   invoke-static {}, LMain;->assertCallerIsManaged()V
+
+   return-void
+
+.end method
+
+.method public static runStraightLine(Ljava/lang/Object;Ljava/lang/Object;)V
+   .registers 3
+
+   invoke-static {}, LMain;->assertCallerIsManaged()V
+
+   monitor-enter v1      # 1
+   monitor-enter v2      # 2
+
+   monitor-exit v2       # 2
+   monitor-exit v1       # 1
+
+   return-void
+
+.end method
+
+.method public static runBalancedJoin(Ljava/lang/Object;Ljava/lang/Object;)V
+   .registers 3
+
+   invoke-static {}, LMain;->assertCallerIsManaged()V
+
+   monitor-enter v1      # 1
+
+   if-eqz v2, :Lnull
+
+:LnotNull
+
+   monitor-enter v2      # 2
+   goto :Lend
+
+:Lnull
+   monitor-enter v2      # 2
+
+:Lend
+
+   monitor-exit v2       # 2
+   monitor-exit v1       # 1
+
+   return-void
+
+.end method
diff --git a/test/088-monitor-verification/smali/TooDeep.smali b/test/088-monitor-verification/smali/TooDeep.smali
new file mode 100644
index 0000000..1a8f2f0
--- /dev/null
+++ b/test/088-monitor-verification/smali/TooDeep.smali
@@ -0,0 +1,82 @@
+.class public LTooDeep;
+
+.super Ljava/lang/Object;
+
+.method public static run(Ljava/lang/Object;)V
+   .registers 3
+
+   # Lock depth is 33, which is more than the verifier supports. This should have been punted to
+   # the interpreter.
+   invoke-static {}, LMain;->assertCallerIsInterpreted()V
+
+   monitor-enter v2        #  1
+   monitor-enter v2        #  2
+   monitor-enter v2        #  3
+   monitor-enter v2        #  4
+   monitor-enter v2        #  5
+   monitor-enter v2        #  6
+   monitor-enter v2        #  7
+   monitor-enter v2        #  8
+   monitor-enter v2        #  9
+   monitor-enter v2        # 10
+   monitor-enter v2        # 11
+   monitor-enter v2        # 12
+   monitor-enter v2        # 13
+   monitor-enter v2        # 14
+   monitor-enter v2        # 15
+   monitor-enter v2        # 16
+   monitor-enter v2        # 17
+   monitor-enter v2        # 18
+   monitor-enter v2        # 19
+   monitor-enter v2        # 20
+   monitor-enter v2        # 21
+   monitor-enter v2        # 22
+   monitor-enter v2        # 23
+   monitor-enter v2        # 24
+   monitor-enter v2        # 25
+   monitor-enter v2        # 26
+   monitor-enter v2        # 27
+   monitor-enter v2        # 28
+   monitor-enter v2        # 29
+   monitor-enter v2        # 30
+   monitor-enter v2        # 31
+   monitor-enter v2        # 32
+   monitor-enter v2        # 33
+
+   monitor-exit v2         #  1
+   monitor-exit v2         #  2
+   monitor-exit v2         #  3
+   monitor-exit v2         #  4
+   monitor-exit v2         #  5
+   monitor-exit v2         #  6
+   monitor-exit v2         #  7
+   monitor-exit v2         #  8
+   monitor-exit v2         #  9
+   monitor-exit v2         # 10
+   monitor-exit v2         # 11
+   monitor-exit v2         # 12
+   monitor-exit v2         # 13
+   monitor-exit v2         # 14
+   monitor-exit v2         # 15
+   monitor-exit v2         # 16
+   monitor-exit v2         # 17
+   monitor-exit v2         # 18
+   monitor-exit v2         # 19
+   monitor-exit v2         # 20
+   monitor-exit v2         # 21
+   monitor-exit v2         # 22
+   monitor-exit v2         # 23
+   monitor-exit v2         # 24
+   monitor-exit v2         # 25
+   monitor-exit v2         # 26
+   monitor-exit v2         # 27
+   monitor-exit v2         # 28
+   monitor-exit v2         # 29
+   monitor-exit v2         # 30
+   monitor-exit v2         # 31
+   monitor-exit v2         # 32
+   monitor-exit v2         # 33
+
+   return-void
+
+.end method
diff --git a/test/088-monitor-verification/smali/UnbalancedJoin.smali b/test/088-monitor-verification/smali/UnbalancedJoin.smali
new file mode 100644
index 0000000..da8f773
--- /dev/null
+++ b/test/088-monitor-verification/smali/UnbalancedJoin.smali
@@ -0,0 +1,31 @@
+.class public LUnbalancedJoin;
+
+.super Ljava/lang/Object;
+
+.method public static run(Ljava/lang/Object;Ljava/lang/Object;)V
+   .registers 3
+
+   invoke-static {}, LMain;->assertCallerIsInterpreted()V
+
+   if-eqz v2, :Lnull
+
+:LnotNull
+
+   monitor-enter v1      # 1
+   monitor-enter v2      # 2
+   goto :Lend
+
+:Lnull
+   monitor-enter v2      # 1
+   monitor-enter v1      # 2
+
+:Lend
+
+   # Lock levels are "opposite" for the joined flows.
+
+   monitor-exit v2       # 2
+   monitor-exit v1       # 1
+
+   return-void
+
+.end method
diff --git a/test/088-monitor-verification/smali/UnbalancedStraight.smali b/test/088-monitor-verification/smali/UnbalancedStraight.smali
new file mode 100644
index 0000000..68edb6c
--- /dev/null
+++ b/test/088-monitor-verification/smali/UnbalancedStraight.smali
@@ -0,0 +1,18 @@
+.class public LUnbalancedStraight;
+
+.super Ljava/lang/Object;
+
+.method public static run(Ljava/lang/Object;Ljava/lang/Object;)V
+   .registers 3
+
+   invoke-static {}, LMain;->assertCallerIsInterpreted()V
+
+   monitor-enter v1      # 1
+   monitor-enter v2      # 2
+
+   monitor-exit v1       # 1     Unbalanced unlock.
+   monitor-exit v2       # 2
+
+   return-void
+
+.end method
diff --git a/test/088-monitor-verification/src/Main.java b/test/088-monitor-verification/src/Main.java
index b60c71e..53b72e9 100644
--- a/test/088-monitor-verification/src/Main.java
+++ b/test/088-monitor-verification/src/Main.java
@@ -14,6 +14,9 @@
  * limitations under the License.
  */
 
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
 
 /*
  * Entry point and tests that are expected to succeed.
@@ -23,6 +26,7 @@
      * Drives tests.
      */
     public static void main(String[] args) {
+        System.loadLibrary(args[0]);
         Main m = new Main();
 
         m.recursiveSync(0);
@@ -38,11 +42,6 @@
         System.out.println("constantLock ok");
 
         m.notExcessiveNesting();
-        try {
-            TooDeep.excessiveNesting();
-            System.err.println("excessiveNesting did not throw");
-        } catch (VerifyError ve) {}
-        System.out.println("excessiveNesting ok");
 
         m.notNested();
         System.out.println("notNested ok");
@@ -55,6 +54,8 @@
 
         m.triplet(obj1, obj2, 0);
         System.out.println("triplet ok");
+
+        runSmaliTests();
     }
 
     /**
@@ -216,4 +217,62 @@
 
         doNothing(localObj);
     }
+
+    // Smali testing code.
+    private static void runSmaliTests() {
+        runTest("OK", new Object[] { new Object(), new Object() }, null);
+        runTest("TooDeep", new Object[] { new Object() }, null);
+        runTest("NotStructuredOverUnlock", new Object[] { new Object() },
+                IllegalMonitorStateException.class);
+        runTest("NotStructuredUnderUnlock", new Object[] { new Object() }, null);
+                // TODO: new IllegalMonitorStateException());
+        runTest("UnbalancedJoin", new Object[] { new Object(), new Object() }, null);
+        runTest("UnbalancedStraight", new Object[] { new Object(), new Object() }, null);
+    }
+
+    private static void runTest(String className, Object[] parameters, Class<?> excType) {
+        System.out.println(className);
+        try {
+            Class<?> c = Class.forName(className);
+
+            Method[] methods = c.getDeclaredMethods();
+
+            // For simplicity we assume that test methods are not overloaded. So searching by name
+            // will give us the method we need to run.
+            Method method = null;
+            for (Method m : methods) {
+                if (m.getName().equals("run")) {
+                    method = m;
+                    break;
+                }
+            }
+
+            if (method == null) {
+                System.out.println("Could not find test method for " + className);
+            } else if (!Modifier.isStatic(method.getModifiers())) {
+                System.out.println("Test method for " + className + " is not static.");
+            } else {
+                method.invoke(null, parameters);
+                if (excType != null) {
+                    System.out.println("Expected an exception in " + className);
+                }
+            }
+        } catch (Throwable exc) {
+            if (excType == null) {
+                System.out.println("Did not expect exception " + exc + " for " + className);
+                exc.printStackTrace(System.out);
+            } else if (exc instanceof InvocationTargetException && exc.getCause() != null &&
+                       exc.getCause().getClass().equals(excType)) {
+                // Expected exception is wrapped in InvocationTargetException.
+            } else if (!excType.equals(exc.getClass())) {
+                System.out.println("Expected " + excType.getName() + ", but got " + exc.getClass());
+            } else {
+              // Expected exception, do nothing.
+            }
+        }
+    }
+
+    // Helpers for the smali code.
+    public static native void assertCallerIsInterpreted();
+    public static native void assertCallerIsManaged();
 }
diff --git a/test/088-monitor-verification/src/TooDeep.java b/test/088-monitor-verification/src/TooDeep.java
deleted file mode 100644
index 76192e5..0000000
--- a/test/088-monitor-verification/src/TooDeep.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-/**
- * The class has a method with too many levels of nested "synchronized"
- * blocks.  The verifier will reject it.
- *
- * (It would be perfectly okay if the verifier *didn't* reject this.
- * The goal here is just to exercise the failure path.  It also serves
- * as a check to see if the monitor checks are enabled.)
- */
-public class TooDeep {
-
-    public static void excessiveNesting() {
-        synchronized (TooDeep.class) {   // 1
-        synchronized (TooDeep.class) {   // 2
-        synchronized (TooDeep.class) {   // 3
-        synchronized (TooDeep.class) {   // 4
-        synchronized (TooDeep.class) {   // 5
-        synchronized (TooDeep.class) {   // 6
-        synchronized (TooDeep.class) {   // 7
-        synchronized (TooDeep.class) {   // 8
-        synchronized (TooDeep.class) {   // 9
-        synchronized (TooDeep.class) {   // 10
-        synchronized (TooDeep.class) {   // 11
-        synchronized (TooDeep.class) {   // 12
-        synchronized (TooDeep.class) {   // 13
-        synchronized (TooDeep.class) {   // 14
-        synchronized (TooDeep.class) {   // 15
-        synchronized (TooDeep.class) {   // 16
-        synchronized (TooDeep.class) {   // 17
-        synchronized (TooDeep.class) {   // 18
-        synchronized (TooDeep.class) {   // 19
-        synchronized (TooDeep.class) {   // 20
-        synchronized (TooDeep.class) {   // 21
-        synchronized (TooDeep.class) {   // 22
-        synchronized (TooDeep.class) {   // 23
-        synchronized (TooDeep.class) {   // 24
-        synchronized (TooDeep.class) {   // 25
-        synchronized (TooDeep.class) {   // 26
-        synchronized (TooDeep.class) {   // 27
-        synchronized (TooDeep.class) {   // 28
-        synchronized (TooDeep.class) {   // 29
-        synchronized (TooDeep.class) {   // 30
-        synchronized (TooDeep.class) {   // 31
-        synchronized (TooDeep.class) {   // 32
-        synchronized (TooDeep.class) {   // 33
-        }}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}
-    }
-}
diff --git a/test/088-monitor-verification/stack_inspect.cc b/test/088-monitor-verification/stack_inspect.cc
new file mode 100644
index 0000000..e2899c3
--- /dev/null
+++ b/test/088-monitor-verification/stack_inspect.cc
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "jni.h"
+
+#include "base/logging.h"
+#include "dex_file-inl.h"
+#include "mirror/class-inl.h"
+#include "nth_caller_visitor.h"
+#include "runtime.h"
+#include "scoped_thread_state_change.h"
+#include "stack.h"
+#include "thread-inl.h"
+
+namespace art {
+
+// public static native void assertCallerIsInterpreted();
+
+extern "C" JNIEXPORT void JNICALL Java_Main_assertCallerIsInterpreted(JNIEnv* env, jclass) {
+  LOG(INFO) << "assertCallerIsInterpreted";
+
+  ScopedObjectAccess soa(env);
+  NthCallerVisitor caller(soa.Self(), 1, false);
+  caller.WalkStack();
+  CHECK(caller.caller != nullptr);
+  LOG(INFO) << PrettyMethod(caller.caller);
+  CHECK(caller.GetCurrentShadowFrame() != nullptr);
+}
+
+// public static native void assertCallerIsManaged();
+
+extern "C" JNIEXPORT void JNICALL Java_Main_assertCallerIsManaged(JNIEnv* env, jclass cls) {
+  // Note: needs some smarts to not fail if there is no managed code, at all.
+  LOG(INFO) << "assertCallerIsManaged";
+
+  ScopedObjectAccess soa(env);
+
+  mirror::Class* klass = soa.Decode<mirror::Class*>(cls);
+  const DexFile& dex_file = klass->GetDexFile();
+  const OatFile::OatDexFile* oat_dex_file = dex_file.GetOatDexFile();
+  if (oat_dex_file == nullptr) {
+    // No oat file, this must be a test configuration that doesn't compile at all. Ignore that the
+    // result will be that we're running the interpreter.
+    return;
+  }
+
+  NthCallerVisitor caller(soa.Self(), 1, false);
+  caller.WalkStack();
+  CHECK(caller.caller != nullptr);
+  LOG(INFO) << PrettyMethod(caller.caller);
+
+  if (caller.GetCurrentShadowFrame() == nullptr) {
+    // Not a shadow frame, this looks good.
+    return;
+  }
+
+  // This could be an interpret-only or a verify-at-runtime compilation, or a read-barrier variant,
+  // or... It's not really safe to just reject now. Let's look at the access flags. If the method
+  // was successfully verified, its access flags should be set to mark it preverified, except when
+  // we're running soft-fail tests.
+  if (Runtime::Current()->IsVerificationSoftFail()) {
+    // Soft-fail config. Everything should be running with interpreter access checks, potentially.
+    return;
+  }
+  CHECK(caller.caller->IsPreverified());
+}
+
+}  // namespace art
diff --git a/test/101-fibonacci/src/Main.java b/test/101-fibonacci/src/Main.java
index 3773e1b..c594edb 100644
--- a/test/101-fibonacci/src/Main.java
+++ b/test/101-fibonacci/src/Main.java
@@ -43,7 +43,7 @@
     }
 
     public static void main(String[] args) {
-        String arg = (args.length > 0) ? args[0] : "10";
+        String arg = (args.length > 1) ? args[1] : "10";
         try {
             int x = Integer.parseInt(arg);
             int y = fibonacci(x);
diff --git a/test/115-native-bridge/nativebridge.cc b/test/115-native-bridge/nativebridge.cc
index 702e779..948273a 100644
--- a/test/115-native-bridge/nativebridge.cc
+++ b/test/115-native-bridge/nativebridge.cc
@@ -206,9 +206,9 @@
 #if defined(__arm__) || defined(__i386__) || defined(__aarch64__)
   *go_away_compiler = 'a';
 #elif defined(__x86_64__)
-  // Cause a SEGV using an instruction known to be 3 bytes long to account for hardcoded jump
+  // Cause a SEGV using an instruction known to be 2 bytes long to account for hardcoded jump
   // in the signal handler
-  asm volatile("movl $0, %%eax;" "movb $1, (%%al);" : : : "%eax");
+  asm volatile("movl $0, %%eax;" "movb %%ah, (%%rax);" : : : "%eax");
 #else
   // On other architectures we simulate SEGV.
   kill(getpid(), SIGSEGV);
@@ -403,9 +403,12 @@
     struct ucontext *uc = reinterpret_cast<struct ucontext*>(context);
     struct sigcontext *sc = reinterpret_cast<struct sigcontext*>(&uc->uc_mcontext);
     sc->pc += 4;          // Skip instruction causing segv & sigill.
-#elif defined(__i386__) || defined(__x86_64__)
+#elif defined(__i386__)
     struct ucontext *uc = reinterpret_cast<struct ucontext*>(context);
     uc->CTX_EIP += 3;
+#elif defined(__x86_64__)
+    struct ucontext *uc = reinterpret_cast<struct ucontext*>(context);
+    uc->CTX_EIP += 2;
 #else
     UNUSED(context);
 #endif
diff --git a/test/115-native-bridge/run b/test/115-native-bridge/run
index 32a9975..ea2045b 100644
--- a/test/115-native-bridge/run
+++ b/test/115-native-bridge/run
@@ -20,7 +20,9 @@
 LIBPATH=$(echo ${ARGS} | sed -r 's/.*Djava.library.path=([^ ]*) .*/\1/')
 ln -s ${LIBPATH}/libnativebridgetest.so .
 touch libarttest.so
+touch libarttestd.so
 ln -s ${LIBPATH}/libarttest.so libarttest2.so
+ln -s ${LIBPATH}/libarttestd.so libarttestd2.so
 
 # pwd likely has /, so it's a pain to put that into a sed rule.
 LEFT=$(echo ${ARGS} | sed -r 's/-Djava.library.path.*//')
diff --git a/test/115-native-bridge/src/NativeBridgeMain.java b/test/115-native-bridge/src/NativeBridgeMain.java
index 25390f7..c298b1b 100644
--- a/test/115-native-bridge/src/NativeBridgeMain.java
+++ b/test/115-native-bridge/src/NativeBridgeMain.java
@@ -189,7 +189,7 @@
     static public void main(String[] args) throws Exception {
         System.out.println("Ready for native bridge tests.");
 
-        System.loadLibrary("arttest");
+        System.loadLibrary(args[0]);
 
         Main.main(null);
     }
diff --git a/test/116-nodex2oat/src/Main.java b/test/116-nodex2oat/src/Main.java
index 37ac9d5..086ffb9 100644
--- a/test/116-nodex2oat/src/Main.java
+++ b/test/116-nodex2oat/src/Main.java
@@ -16,6 +16,7 @@
 
 public class Main {
   public static void main(String[] args) {
+    System.loadLibrary(args[0]);
     System.out.println(
         "Has oat is " + hasOat() + ", is dex2oat enabled is " + isDex2OatEnabled() + ".");
 
@@ -26,10 +27,6 @@
     }
   }
 
-  static {
-    System.loadLibrary("arttest");
-  }
-
   private native static boolean hasOat();
 
   private native static boolean isDex2OatEnabled();
diff --git a/test/117-nopatchoat/src/Main.java b/test/117-nopatchoat/src/Main.java
index 7bc9dbb..223e120 100644
--- a/test/117-nopatchoat/src/Main.java
+++ b/test/117-nopatchoat/src/Main.java
@@ -16,6 +16,8 @@
 
 public class Main {
   public static void main(String[] args) {
+    System.loadLibrary(args[0]);
+
     boolean executable_correct = (isPic() ?
                                   hasExecutableOat() == true :
                                   hasExecutableOat() == isDex2OatEnabled());
@@ -41,10 +43,6 @@
     return ret.substring(0, ret.length() - 1);
   }
 
-  static {
-    System.loadLibrary("arttest");
-  }
-
   private native static boolean isDex2OatEnabled();
 
   private native static boolean isPic();
diff --git a/test/118-noimage-dex2oat/src/Main.java b/test/118-noimage-dex2oat/src/Main.java
index 9bf5bb3..dba9166 100644
--- a/test/118-noimage-dex2oat/src/Main.java
+++ b/test/118-noimage-dex2oat/src/Main.java
@@ -19,6 +19,7 @@
 
 public class Main {
   public static void main(String[] args) throws Exception {
+    System.loadLibrary(args[0]);
     boolean hasImage = hasImage();
     String instructionSet = VMRuntime.getCurrentInstructionSet();
     boolean isBootClassPathOnDisk = VMRuntime.isBootClassPathOnDisk(instructionSet);
@@ -41,10 +42,6 @@
     testB18485243();
   }
 
-  static {
-    System.loadLibrary("arttest");
-  }
-
   private native static boolean hasImage();
 
   private native static boolean isImageDex2OatEnabled();
diff --git a/test/119-noimage-patchoat/src/Main.java b/test/119-noimage-patchoat/src/Main.java
index 11c736a..6a70f58 100644
--- a/test/119-noimage-patchoat/src/Main.java
+++ b/test/119-noimage-patchoat/src/Main.java
@@ -16,6 +16,7 @@
 
 public class Main {
   public static void main(String[] args) {
+    System.loadLibrary(args[0]);
     boolean hasImage = hasImage();
     System.out.println(
         "Has image is " + hasImage + ", is image dex2oat enabled is "
@@ -28,10 +29,6 @@
     }
   }
 
-  static {
-    System.loadLibrary("arttest");
-  }
-
   private native static boolean hasImage();
 
   private native static boolean isImageDex2OatEnabled();
diff --git a/test/131-structural-change/src/Main.java b/test/131-structural-change/src/Main.java
index 8dfa280..6cbbd12 100644
--- a/test/131-structural-change/src/Main.java
+++ b/test/131-structural-change/src/Main.java
@@ -23,6 +23,7 @@
  */
 public class Main {
     public static void main(String[] args) {
+        System.loadLibrary(args[0]);
         new Main().run();
     }
 
@@ -49,9 +50,5 @@
         System.out.println("Done.");
     }
 
-    static {
-        System.loadLibrary("arttest");
-    }
-
     private native static boolean hasOat();
 }
diff --git a/test/134-nodex2oat-nofallback/src/Main.java b/test/134-nodex2oat-nofallback/src/Main.java
index 37ac9d5..086ffb9 100644
--- a/test/134-nodex2oat-nofallback/src/Main.java
+++ b/test/134-nodex2oat-nofallback/src/Main.java
@@ -16,6 +16,7 @@
 
 public class Main {
   public static void main(String[] args) {
+    System.loadLibrary(args[0]);
     System.out.println(
         "Has oat is " + hasOat() + ", is dex2oat enabled is " + isDex2OatEnabled() + ".");
 
@@ -26,10 +27,6 @@
     }
   }
 
-  static {
-    System.loadLibrary("arttest");
-  }
-
   private native static boolean hasOat();
 
   private native static boolean isDex2OatEnabled();
diff --git a/test/137-cfi/src/Main.java b/test/137-cfi/src/Main.java
index 6cd187a..dc3ef7e 100644
--- a/test/137-cfi/src/Main.java
+++ b/test/137-cfi/src/Main.java
@@ -41,6 +41,7 @@
   }
 
   public static void main(String[] args) throws Exception {
+    System.loadLibrary(args[0]);
       boolean secondary = false;
       if (args.length > 0 && args[args.length - 1].equals("--secondary")) {
           secondary = true;
@@ -48,10 +49,6 @@
       new Main(secondary).run();
   }
 
-  static {
-      System.loadLibrary("arttest");
-  }
-
   private void run() {
       if (secondary) {
           if (!TEST_REMOTE_UNWINDING) {
diff --git a/test/139-register-natives/src/Main.java b/test/139-register-natives/src/Main.java
index 35b2f9c..8dd2131 100644
--- a/test/139-register-natives/src/Main.java
+++ b/test/139-register-natives/src/Main.java
@@ -16,15 +16,12 @@
 
 public class Main {
   public static void main(String[] args) {
+    System.loadLibrary(args[0]);
     testRegistration1();
     testRegistration2();
     testRegistration3();
   }
 
-  static {
-    System.loadLibrary("arttest");
-  }
-
   // Test that a subclass' method is registered instead of a superclass' method.
   private static void testRegistration1() {
     registerNatives(TestSub.class);
diff --git a/test/454-get-vreg/src/Main.java b/test/454-get-vreg/src/Main.java
index df07d44..95d4190 100644
--- a/test/454-get-vreg/src/Main.java
+++ b/test/454-get-vreg/src/Main.java
@@ -36,11 +36,8 @@
     return 42;
   }
 
-  static {
-    System.loadLibrary("arttest");
-  }
-
   public static void main(String[] args) {
+    System.loadLibrary(args[0]);
     Main rm = new Main();
     if (rm.testSimpleVReg(1, 1.0f, (short)2, true, (byte)3, 'c') != 43) {
       throw new Error("Expected 43");
diff --git a/test/455-set-vreg/src/Main.java b/test/455-set-vreg/src/Main.java
index 2172d92..4db9d66 100644
--- a/test/455-set-vreg/src/Main.java
+++ b/test/455-set-vreg/src/Main.java
@@ -40,11 +40,8 @@
 
   native void doNativeCallSetVReg();
 
-  static {
-    System.loadLibrary("arttest");
-  }
-
   public static void main(String[] args) {
+    System.loadLibrary(args[0]);
     Main rm = new Main();
     int intExpected = 5 - 4 - 3 - 2 - 1;
     int intResult = rm.testIntVReg(0, 0, 0, 0, 0);
diff --git a/test/457-regs/src/Main.java b/test/457-regs/src/Main.java
index 0d82033..3b8df44 100644
--- a/test/457-regs/src/Main.java
+++ b/test/457-regs/src/Main.java
@@ -22,6 +22,8 @@
   class InnerClass {}
 
   public static void main(String[] args) throws Exception {
+    System.loadLibrary(args[0]);
+
     Class<?> c = Class.forName("PhiLiveness");
     Method m = c.getMethod("mergeOk", boolean.class, byte.class);
     m.invoke(null, new Boolean(true), new Byte((byte)2));
@@ -38,8 +40,4 @@
     m = c.getMethod("phiAllEquivalents", Main.class);
     m.invoke(null, new Main());
   }
-
-  static {
-    System.loadLibrary("arttest");
-  }
 }
diff --git a/test/461-get-reference-vreg/src/Main.java b/test/461-get-reference-vreg/src/Main.java
index a94c6fb..f7d4356 100644
--- a/test/461-get-reference-vreg/src/Main.java
+++ b/test/461-get-reference-vreg/src/Main.java
@@ -38,11 +38,8 @@
   native int doNativeCallRef();
   static native int doStaticNativeCallRef();
 
-  static {
-    System.loadLibrary("arttest");
-  }
-
   public static void main(String[] args) {
+    System.loadLibrary(args[0]);
     Main rm = new Main();
     if (rm.testThisWithInstanceCall() != 1) {
       throw new Error("Expected 1");
diff --git a/test/466-get-live-vreg/src/Main.java b/test/466-get-live-vreg/src/Main.java
index 851506b..d036a24 100644
--- a/test/466-get-live-vreg/src/Main.java
+++ b/test/466-get-live-vreg/src/Main.java
@@ -48,11 +48,8 @@
 
   static native void doStaticNativeCallLiveVreg();
 
-  static {
-    System.loadLibrary("arttest");
-  }
-
   public static void main(String[] args) {
+    System.loadLibrary(args[0]);
     if (testLiveArgument(staticField3) != staticField3) {
       throw new Error("Expected " + staticField3);
     }
diff --git a/test/474-fp-sub-neg/expected.txt b/test/474-fp-sub-neg/expected.txt
index 1c15abb..1c7ded3 100644
--- a/test/474-fp-sub-neg/expected.txt
+++ b/test/474-fp-sub-neg/expected.txt
@@ -1,6 +1,13 @@
 -0.0
-0.0
-0.0
 -0.0
 0.0
 0.0
+0.0
+0.0
+-0.0
+-0.0
+0.0
+0.0
+0.0
+0.0
+d 0.0
diff --git a/test/474-fp-sub-neg/src/Main.java b/test/474-fp-sub-neg/src/Main.java
index c190e8e..796d56c 100644
--- a/test/474-fp-sub-neg/src/Main.java
+++ b/test/474-fp-sub-neg/src/Main.java
@@ -17,33 +17,58 @@
 public class Main {
     public static void floatTest() {
       float f = 0;
+      float nf = -0;
       float fc = 1f;
       for (int i = 0; i < 2; i++) {
         f -= fc;
         f = -f;
+        nf -= fc;
+        nf = -nf;
       }
 
       System.out.println(f);
+      System.out.println(nf);
       System.out.println(f + 0f);
       System.out.println(f - (-0f));
+      System.out.println(-f - (-nf));
+      System.out.println(-f + (-nf));
     }
 
     public static void doubleTest() {
       double d = 0;
+      double nd = -0;
       double dc = 1f;
       for (int i = 0; i < 2; i++) {
         d -= dc;
         d = -d;
+        nd -= dc;
+        nd = -nd;
       }
 
       System.out.println(d);
+      System.out.println(nd);
       System.out.println(d + 0f);
       System.out.println(d - (-0f));
+      System.out.println(-d - (-nd));
+      System.out.println(-d + (-nd));
+    }
+
+    public static void bug_1() {
+      int i4=18, i3=-48959;
+      float d;
+      float f=-0.0f;
+      float a=0.0f;
+
+      d = -f + (-a);
+      f += i4 * i3;
+
+      System.out.println("d " + d);
     }
 
     public static void main(String[] args) {
         doubleTest();
         floatTest();
+        bug_1();
     }
 
 }
diff --git a/test/497-inlining-and-class-loader/expected.txt b/test/497-inlining-and-class-loader/expected.txt
index 3e1d85e..f5b9fe0 100644
--- a/test/497-inlining-and-class-loader/expected.txt
+++ b/test/497-inlining-and-class-loader/expected.txt
@@ -1,7 +1,7 @@
 java.lang.Exception
-	at Main.$noinline$bar(Main.java:127)
+	at Main.$noinline$bar(Main.java:124)
 	at Level2.$inline$bar(Level1.java:25)
 	at Level1.$inline$bar(Level1.java:19)
 	at LoadedByMyClassLoader.bar(Main.java:82)
 	at java.lang.reflect.Method.invoke(Native Method)
-	at Main.main(Main.java:101)
+	at Main.main(Main.java:98)
diff --git a/test/497-inlining-and-class-loader/src/Main.java b/test/497-inlining-and-class-loader/src/Main.java
index 0f7eb59..832b1f0 100644
--- a/test/497-inlining-and-class-loader/src/Main.java
+++ b/test/497-inlining-and-class-loader/src/Main.java
@@ -84,11 +84,8 @@
 }
 
 class Main {
-  static {
-    System.loadLibrary("arttest");
-  }
-
   public static void main(String[] args) throws Exception {
+    System.loadLibrary(args[0]);
     // Clone resolved methods, to restore the original version just
     // before we walk the stack in $noinline$bar.
     savedResolvedMethods = cloneResolvedMethods(Main.class);
diff --git a/test/Android.libarttest.mk b/test/Android.libarttest.mk
index fcb9f8a..82f8c79 100644
--- a/test/Android.libarttest.mk
+++ b/test/Android.libarttest.mk
@@ -25,6 +25,7 @@
   004-StackWalk/stack_walk_jni.cc \
   004-UnsafeTest/unsafe_test.cc \
   051-thread/thread_test.cc \
+  088-monitor-verification/stack_inspect.cc \
   116-nodex2oat/nodex2oat.cc \
   117-nopatchoat/nopatchoat.cc \
   118-noimage-dex2oat/noimage-dex2oat.cc \
@@ -38,8 +39,10 @@
   497-inlining-and-class-loader/clear_dex_cache.cc
 
 ART_TARGET_LIBARTTEST_$(ART_PHONY_TEST_TARGET_SUFFIX) += $(ART_TARGET_TEST_OUT)/$(TARGET_ARCH)/libarttest.so
+ART_TARGET_LIBARTTEST_$(ART_PHONY_TEST_TARGET_SUFFIX) += $(ART_TARGET_TEST_OUT)/$(TARGET_ARCH)/libarttestd.so
 ifdef TARGET_2ND_ARCH
   ART_TARGET_LIBARTTEST_$(2ND_ART_PHONY_TEST_TARGET_SUFFIX) += $(ART_TARGET_TEST_OUT)/$(TARGET_2ND_ARCH)/libarttest.so
+  ART_TARGET_LIBARTTEST_$(2ND_ART_PHONY_TEST_TARGET_SUFFIX) += $(ART_TARGET_TEST_OUT)/$(TARGET_2ND_ARCH)/libarttestd.so
 endif
 
 # $(1): target or host
@@ -49,17 +52,23 @@
       $$(error expected target or host for argument 1, received $(1))
     endif
   endif
+  ifneq ($(2),d)
+    ifneq ($(2),)
+      $$(error d or empty for argument 2, received $(2))
+    endif
+  endif
 
   art_target_or_host := $(1)
+  suffix := $(2)
 
   include $(CLEAR_VARS)
   LOCAL_CPP_EXTENSION := $(ART_CPP_EXTENSION)
-  LOCAL_MODULE := libarttest
+  LOCAL_MODULE := libarttest$$(suffix)
   ifeq ($$(art_target_or_host),target)
     LOCAL_MODULE_TAGS := tests
   endif
   LOCAL_SRC_FILES := $(LIBARTTEST_COMMON_SRC_FILES)
-  LOCAL_SHARED_LIBRARIES += libartd libbacktrace
+  LOCAL_SHARED_LIBRARIES += libart$$(suffix) libbacktrace
   LOCAL_C_INCLUDES += $(ART_C_INCLUDES) art/runtime
   LOCAL_ADDITIONAL_DEPENDENCIES := art/build/Android.common_build.mk
   LOCAL_ADDITIONAL_DEPENDENCIES += $(LOCAL_PATH)/Android.libarttest.mk
@@ -84,13 +93,16 @@
 
   # Clear locally used variables.
   art_target_or_host :=
+  suffix :=
 endef
 
 ifeq ($(ART_BUILD_TARGET),true)
-  $(eval $(call build-libarttest,target))
+  $(eval $(call build-libarttest,target,))
+  $(eval $(call build-libarttest,target,d))
 endif
 ifeq ($(ART_BUILD_HOST),true)
-  $(eval $(call build-libarttest,host))
+  $(eval $(call build-libarttest,host,))
+  $(eval $(call build-libarttest,host,d))
 endif
 
 # Clear locally used variables.
diff --git a/test/Android.run-test.mk b/test/Android.run-test.mk
index 4e6df6c..439e423 100644
--- a/test/Android.run-test.mk
+++ b/test/Android.run-test.mk
@@ -378,41 +378,6 @@
       $(PICTEST_TYPES),$(DEBUGGABLE_TYPES), $(TEST_ART_BROKEN_TRACING_RUN_TESTS),$(ALL_ADDRESS_SIZES))
 endif
 
-TEST_ART_BROKEN_TRACING_RUN_TESTS :=
-
-# The following tests use libarttest.so, which is linked against libartd.so, so will
-# not work when libart.so is the one loaded.
-# TODO: Find a way to run these tests in ndebug mode.
-TEST_ART_BROKEN_NDEBUG_TESTS := \
-  004-JniTest \
-  004-ReferenceMap \
-  004-SignalTest \
-  004-StackWalk \
-  004-UnsafeTest \
-  051-thread \
-  115-native-bridge \
-  116-nodex2oat \
-  117-nopatchoat \
-  118-noimage-dex2oat \
-  119-noimage-patchoat \
-  131-structural-change \
-  137-cfi \
-  139-register-natives \
-  454-get-vreg \
-  455-set-vreg \
-  457-regs \
-  461-get-reference-vreg \
-  466-get-live-vreg \
-  497-inlining-and-class-loader \
-
-ifneq (,$(filter ndebug,$(RUN_TYPES)))
-  ART_TEST_KNOWN_BROKEN += $(call all-run-test-names,$(TARGET_TYPES),ndebug,$(PREBUILD_TYPES), \
-      $(COMPILER_TYPES), $(RELOCATE_TYPES),$(TRACE_TYPES),$(GC_TYPES),$(JNI_TYPES),$(IMAGE_TYPES), \
-      $(PICTEST_TYPES),$(DEBUGGABLE_TYPES),$(TEST_ART_BROKEN_NDEBUG_TESTS),$(ALL_ADDRESS_SIZES))
-endif
-
-TEST_ART_BROKEN_NDEBUG_TESTS :=
-
 # Known broken tests for the interpreter.
 # CFI unwinding expects managed frames.
 TEST_ART_BROKEN_INTERPRETER_RUN_TESTS := \
@@ -602,8 +567,10 @@
 
 # Also need libarttest.
 TEST_ART_TARGET_SYNC_DEPS += $(ART_TARGET_TEST_OUT)/$(TARGET_ARCH)/libarttest.so
+TEST_ART_TARGET_SYNC_DEPS += $(ART_TARGET_TEST_OUT)/$(TARGET_ARCH)/libarttestd.so
 ifdef TARGET_2ND_ARCH
 TEST_ART_TARGET_SYNC_DEPS += $(ART_TARGET_TEST_OUT)/$(TARGET_2ND_ARCH)/libarttest.so
+TEST_ART_TARGET_SYNC_DEPS += $(ART_TARGET_TEST_OUT)/$(TARGET_2ND_ARCH)/libarttestd.so
 endif
 
 # Also need libnativebridgetest.
@@ -617,12 +584,14 @@
 ART_TEST_HOST_RUN_TEST_DEPENDENCIES := \
   $(ART_HOST_EXECUTABLES) \
   $(ART_HOST_OUT_SHARED_LIBRARIES)/libarttest$(ART_HOST_SHLIB_EXTENSION) \
+  $(ART_HOST_OUT_SHARED_LIBRARIES)/libarttestd$(ART_HOST_SHLIB_EXTENSION) \
   $(ART_HOST_OUT_SHARED_LIBRARIES)/libnativebridgetest$(ART_HOST_SHLIB_EXTENSION) \
   $(ART_HOST_OUT_SHARED_LIBRARIES)/libjavacore$(ART_HOST_SHLIB_EXTENSION)
 
 ifneq ($(HOST_PREFER_32_BIT),true)
 ART_TEST_HOST_RUN_TEST_DEPENDENCIES += \
   $(2ND_ART_HOST_OUT_SHARED_LIBRARIES)/libarttest$(ART_HOST_SHLIB_EXTENSION) \
+  $(2ND_ART_HOST_OUT_SHARED_LIBRARIES)/libarttestd$(ART_HOST_SHLIB_EXTENSION) \
   $(2ND_ART_HOST_OUT_SHARED_LIBRARIES)/libnativebridgetest$(ART_HOST_SHLIB_EXTENSION) \
   $(2ND_ART_HOST_OUT_SHARED_LIBRARIES)/libjavacore$(ART_HOST_SHLIB_EXTENSION)
 endif
diff --git a/test/StackWalk2/StackWalk2.java b/test/StackWalk2/StackWalk2.java
index a879b46..5e7b22c 100644
--- a/test/StackWalk2/StackWalk2.java
+++ b/test/StackWalk2/StackWalk2.java
@@ -50,11 +50,8 @@
 
   native int refmap2(int x);
 
-  static {
-    System.loadLibrary("arttest");
-  }
-
   public static void main(String[] args) {
+    System.loadLibrary(args[0]);
     StackWalk2 st = new StackWalk2();
     st.f();
   }
diff --git a/test/dexdump/run-all-tests b/test/dexdump/run-all-tests
index d9f1e96..9cf7ab6 100755
--- a/test/dexdump/run-all-tests
+++ b/test/dexdump/run-all-tests
@@ -43,7 +43,7 @@
 DEXDFLAGS2="-l xml"
 
 # Set up dexlist binary and flags to test.
-DEXL="${ANDROID_HOST_OUT}/bin/dexlist2"
+DEXL="${ANDROID_HOST_OUT}/bin/dexlist"
 DEXLFLAGS=""
 
 # Run the tests.
diff --git a/test/etc/run-test-jar b/test/etc/run-test-jar
index a1af577..39dc030 100755
--- a/test/etc/run-test-jar
+++ b/test/etc/run-test-jar
@@ -47,6 +47,7 @@
 DEX_VERIFY=""
 USE_DEX2OAT_AND_PATCHOAT="y"
 INSTRUCTION_SET_FEATURES=""
+ARGS=""
 
 while true; do
     if [ "x$1" = "x--quiet" ]; then
@@ -60,6 +61,14 @@
         fi
         LIB="$1"
         shift
+    elif [ "x$1" = "x--testlib" ]; then
+        shift
+        if [ "x$1" = "x" ]; then
+            echo "$0 missing argument to --testlib" 1>&2
+            exit 1
+        fi
+       	ARGS="${ARGS} $1"
+        shift
     elif [ "x$1" = "x-Xcompiler-option" ]; then
         shift
         option="$1"
@@ -369,7 +378,7 @@
                   $INT_OPTS \
                   $DEBUGGER_OPTS \
                   $DALVIKVM_BOOT_OPT \
-                  -cp $DEX_LOCATION/$TEST_NAME.jar$SECONDARY_DEX $MAIN"
+                  -cp $DEX_LOCATION/$TEST_NAME.jar$SECONDARY_DEX $MAIN $ARGS"
 
 # Remove whitespace.
 dex2oat_cmdline=$(echo $dex2oat_cmdline)
diff --git a/test/run-test b/test/run-test
index 84c818b..424c2e4 100755
--- a/test/run-test
+++ b/test/run-test
@@ -119,6 +119,7 @@
 cfg_output="graph.cfg"
 strace_output="strace-output.txt"
 lib="libartd.so"
+testlib="arttestd"
 run_args="--quiet"
 build_args=""
 
@@ -164,6 +165,7 @@
         shift
     elif [ "x$1" = "x-O" ]; then
         lib="libart.so"
+        testlib="arttest"
         shift
     elif [ "x$1" = "x--dalvik" ]; then
         lib="libdvm.so"
@@ -644,6 +646,10 @@
   fi
 fi
 
+if [ "$runtime" != "jvm" ]; then
+  run_args="${run_args} --testlib ${testlib}"
+fi
+
 # To cause tests to fail fast, limit the file sizes created by dx, dex2oat and ART output to 2MB.
 build_file_size_limit=2048
 run_file_size_limit=2048
diff --git a/tools/ahat/Android.mk b/tools/ahat/Android.mk
new file mode 100644
index 0000000..3c1522c
--- /dev/null
+++ b/tools/ahat/Android.mk
@@ -0,0 +1,58 @@
+#
+# 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.
+#
+
+LOCAL_PATH := $(call my-dir)
+
+# --- ahat.jar ----------------
+include $(CLEAR_VARS)
+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
+LOCAL_IS_HOST_MODULE := true
+LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE := ahat
+include $(BUILD_HOST_JAVA_LIBRARY)
+
+# --- ahat script ----------------
+include $(CLEAR_VARS)
+LOCAL_IS_HOST_MODULE := true
+LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE_CLASS := EXECUTABLES
+LOCAL_MODULE := ahat
+include $(BUILD_SYSTEM)/base_rules.mk
+$(LOCAL_BUILT_MODULE): $(LOCAL_PATH)/ahat $(ACP)
+	@echo "Copy: $(PRIVATE_MODULE) ($@)"
+	$(copy-file-to-new-target)
+	$(hide) chmod 755 $@
+
+ahat: $(LOCAL_BUILT_MODULE)
+
+# --- ahat-test.jar --------------
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := $(call all-java-files-under, test)
+LOCAL_JAR_MANIFEST := test/manifest.txt
+LOCAL_STATIC_JAVA_LIBRARIES := ahat junit
+LOCAL_IS_HOST_MODULE := true
+LOCAL_MODULE_TAGS := tests
+LOCAL_MODULE := ahat-tests
+include $(BUILD_HOST_JAVA_LIBRARY)
+
+ahat-test: $(LOCAL_BUILT_MODULE)
+	java -jar $<
diff --git a/tools/ahat/README.txt b/tools/ahat/README.txt
new file mode 100644
index 0000000..a8e3884
--- /dev/null
+++ b/tools/ahat/README.txt
@@ -0,0 +1,110 @@
+AHAT - Android Heap Analysis Tool
+
+Usage:
+  java -jar ahat.jar [-p port] FILE
+    Launch an http server for viewing the given Android heap-dump FILE.
+
+  Options:
+    -p <port>
+       Serve pages on the given port. Defaults to 7100.
+
+TODO:
+ * Add more tips to the help page.
+   - Note that only 'app' heap matters, not 'zygote' or 'image'.
+   - Say what a dex cache is.
+   - Recommend how to start looking at a heap dump.
+   - Say how to enable allocation sites.
+   - Where to submit feedback, questions, and bug reports.
+ * Submit perflib fix for getting stack traces, then uncomment that code in
+   AhatSnapshot to use that.
+ * Dim 'image' and 'zygote' heap sizes slightly? Why do we even show these?
+ * Filter out RootObjs in mSnapshot.getGCRoots, not RootsHandler.
+ * Let user re-sort sites objects info by clicking column headers.
+ * Let user re-sort "Objects" list.
+ * Show site context and heap and class filter in "Objects" view?
+ * Have a menu at the top of an object view with links to the sections?
+ * Include ahat version and hprof file in the menu at the top of the page?
+ * Heaped Table
+   - Make sortable by clicking on headers.
+   - Use consistent order for heap columns.
+      Sometimes I see "app" first, sometimes last (from one heap dump to
+      another) How about, always sort by name?
+ * For long strings, limit the string length shown in the summary view to
+   something reasonable.  Say 50 chars, then add a "..." at the end.
+ * For string summaries, if the string is an offset into a bigger byte array,
+   make sure to show just the part that's in the bigger byte array, not the
+   entire byte array.
+ * For HeapTable with single heap shown, the heap name isn't centered?
+ * Consistently document functions.
+ * Should help be part of an AhatHandler, that automatically gets the menu and
+   stylesheet link rather than duplicating that?
+ * Show version number with --version.
+ * Show somewhere where to send bugs.
+ * /objects query takes a long time to load without parameters.
+ * Include a link to /objects in the overview and menu?
+ * Turn on LOCAL_JAVACFLAGS := -Xlint:unchecked -Werror
+ * Use hex for object ids in URLs?
+ * In general, all tables and descriptions should show a limited amount to
+   start, and only show more when requested by the user.
+ * Don't have handlers inherit from HttpHandler
+   - because they should be independent from http.
+
+ * [low priority] by site allocations won't line up if the stack has been
+   truncated. Is there any way to manually line them up in that case?
+
+ * [low priority] Have a switch to choose whether unreachable objects are
+   ignored or not?  Is there any interest in what's unreachable, or is it only
+   reachable objects that people care about?
+
+ * [low priority] Have a way to diff two heap dumps by site.
+   This should be pretty easy to do, actually. The interface is the real
+   question. Maybe: augment each byte count field on every page with the diff
+   if a baseline has been provided, and allow the user to sort by the diff.
+
+Things to Test:
+ * That we can open a hprof without an 'app' heap and show a tabulation of
+   objects normally sorted by 'app' heap by default.
+ * Visit /objects without parameters and verify it doesn't throw an exception.
+ * Visit /objects with an invalid site, verify it doesn't throw an exception.
+ * That we can view an array with 3 million elements in a reasonably short
+   amount of time (not more than 1 second?)
+ * That we can view the list of all objects in a reasonably short amount of
+   time.
+ * That we don't show the 'extra' column in the DominatedList if we are
+   showing all the instances.
+
+Reported Issues:
+ * Request to be able to sort tables by size.
+ * Hangs on showing large arrays, where hat does not hang.
+   - Solution is probably to not show all the array elements by default.
+
+Perflib Requests:
+ * Class objects should have java.lang.Class as their class object, not null.
+ * ArrayInstance should have asString() to get the string, without requiring a
+   length function.
+ * Document that getHeapIndex returns -1 for no such heap.
+ * Look up totalRetainedSize for a heap by Heap object, not by a separate heap
+   index.
+ * What's the difference between getId and getUniqueId?
+ * I see objects with duplicate references.
+ * Don't store stack trace by heap (CL 157252)
+ * A way to get overall retained size by heap.
+ * A method Instance.isReachable()
+
+Things to move to perflib:
+ * Extracting the string from a String Instance.
+ * Extracting bitmap data from bitmap instances.
+ * Adding up allocations by stack frame.
+ * Computing, for each instance, the other instances it dominates.
+
+Release History:
+ 0.1ss Aug 04, 2015
+   Enable stack allocations code (using custom modified perflib).
+   Sort objects in 'objects/' with default sort.
+
+ 0.1-stacks Aug 03, 2015
+   Enable stack allocations code (using custom modified perflib).
+
+ 0.1 July 30, 2015
+   Initial Release
+
diff --git a/tools/ahat/ahat b/tools/ahat/ahat
new file mode 100755
index 0000000..77c1d6e
--- /dev/null
+++ b/tools/ahat/ahat
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# Copyright (C) 2015 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+#
+# Wrapper script for calling ahat
+java -jar ${ANDROID_HOST_OUT}/framework/ahat.jar "$@"
diff --git a/tools/ahat/src/AhatHandler.java b/tools/ahat/src/AhatHandler.java
new file mode 100644
index 0000000..2da02f8
--- /dev/null
+++ b/tools/ahat/src/AhatHandler.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ahat;
+
+import com.sun.net.httpserver.HttpExchange;
+import com.sun.net.httpserver.HttpHandler;
+import java.io.IOException;
+import java.io.PrintStream;
+
+/**
+ * AhatHandler.
+ *
+ * Common base class of all the ahat HttpHandlers.
+ */
+abstract class AhatHandler implements HttpHandler {
+
+  protected AhatSnapshot mSnapshot;
+
+  public AhatHandler(AhatSnapshot snapshot) {
+    mSnapshot = snapshot;
+  }
+
+  public abstract void handle(Doc doc, Query query) throws IOException;
+
+  @Override
+  public void handle(HttpExchange exchange) throws IOException {
+    exchange.getResponseHeaders().add("Content-Type", "text/html;charset=utf-8");
+    exchange.sendResponseHeaders(200, 0);
+    PrintStream ps = new PrintStream(exchange.getResponseBody());
+    try {
+      HtmlDoc doc = new HtmlDoc(ps, DocString.text("ahat"), DocString.uri("style.css"));
+      DocString menu = new DocString();
+      menu.appendLink(DocString.uri("/"), DocString.text("overview"));
+      menu.append(" - ");
+      menu.appendLink(DocString.uri("roots"), DocString.text("roots"));
+      menu.append(" - ");
+      menu.appendLink(DocString.uri("sites"), DocString.text("allocations"));
+      menu.append(" - ");
+      menu.appendLink(DocString.uri("help"), DocString.text("help"));
+      doc.menu(menu);
+      handle(doc, new Query(exchange.getRequestURI()));
+      doc.close();
+    } catch (RuntimeException e) {
+      // Print runtime exceptions to standard error for debugging purposes,
+      // because otherwise they are swallowed and not reported.
+      System.err.println("Exception when handling " + exchange.getRequestURI() + ": ");
+      e.printStackTrace();
+      throw e;
+    }
+    ps.close();
+  }
+}
diff --git a/tools/ahat/src/AhatSnapshot.java b/tools/ahat/src/AhatSnapshot.java
new file mode 100644
index 0000000..2437d03
--- /dev/null
+++ b/tools/ahat/src/AhatSnapshot.java
@@ -0,0 +1,197 @@
+/*
+ * 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.RootObj;
+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.Iterables;
+import com.google.common.collect.Lists;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+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 Snapshot mSnapshot;
+  private List<Heap> mHeaps;
+
+  // Map from Instance to the list of Instances it immediately dominates.
+  private Map<Instance, List<Instance>> mDominated;
+
+  private Site mRootSite;
+  private Map<Heap, Long> mHeapSizes;
+
+  public AhatSnapshot(Snapshot snapshot) {
+    mSnapshot = snapshot;
+    mHeaps = new ArrayList<Heap>(mSnapshot.getHeaps());
+    mDominated = new HashMap<Instance, List<Instance>>();
+    mRootSite = new Site("ROOT");
+    mHeapSizes = new HashMap<Heap, Long>();
+
+    ClassObj javaLangClass = mSnapshot.findClass("java.lang.Class");
+    for (Heap heap : mHeaps) {
+      long total = 0;
+      for (Instance inst : Iterables.concat(heap.getClasses(), heap.getInstances())) {
+        Instance dominator = inst.getImmediateDominator();
+        if (dominator != null) {
+          total += inst.getSize();
+
+          // 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);
+        }
+      }
+      mHeapSizes.put(heap, total);
+    }
+  }
+
+  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);
+  }
+
+  public Collection<RootObj> getGCRoots() {
+    return mSnapshot.getGCRoots();
+  }
+
+  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) {
+    // TODO: return inst.getStack() once perflib is fixed.
+    return null;
+  }
+
+  // Return the list of stack frames for a stack trace.
+  private static StackFrame[] getStackFrames(StackTrace stack) {
+    // TODO: Use stack.getFrames() once perflib is fixed.
+    return null;
+  }
+
+  // Return the serial number of the given stack trace.
+  private static int getStackTraceSerialNumber(StackTrace stack) {
+    // TODO: Use stack.getSerialNumber() once perflib is fixed.
+    return 0;
+  }
+
+  // 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;
+  }
+}
diff --git a/tools/ahat/src/BitmapHandler.java b/tools/ahat/src/BitmapHandler.java
new file mode 100644
index 0000000..0f567e3
--- /dev/null
+++ b/tools/ahat/src/BitmapHandler.java
@@ -0,0 +1,68 @@
+/*
+ * 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.Instance;
+import com.sun.net.httpserver.HttpExchange;
+import com.sun.net.httpserver.HttpHandler;
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import javax.imageio.ImageIO;
+
+class BitmapHandler implements HttpHandler {
+  private AhatSnapshot mSnapshot;
+
+  public BitmapHandler(AhatSnapshot snapshot) {
+    mSnapshot = snapshot;
+  }
+
+  @Override
+  public void handle(HttpExchange exchange) throws IOException {
+    try {
+      Query query = new Query(exchange.getRequestURI());
+      long id = query.getLong("id", 0);
+      BufferedImage bitmap = null;
+      Instance inst = mSnapshot.findInstance(id);
+      if (inst != null) {
+        bitmap = InstanceUtils.asBitmap(inst);
+      }
+
+      if (bitmap != null) {
+        exchange.getResponseHeaders().add("Content-Type", "image/png");
+        exchange.sendResponseHeaders(200, 0);
+        OutputStream os = exchange.getResponseBody();
+        ImageIO.write(bitmap, "png", os);
+        os.close();
+      } else {
+        exchange.getResponseHeaders().add("Content-Type", "text/html");
+        exchange.sendResponseHeaders(404, 0);
+        PrintStream ps = new PrintStream(exchange.getResponseBody());
+        HtmlDoc doc = new HtmlDoc(ps, DocString.text("ahat"), DocString.uri("style.css"));
+        doc.big(DocString.text("No bitmap found for the given request."));
+        doc.close();
+      }
+    } catch (RuntimeException e) {
+      // Print runtime exceptions to standard error for debugging purposes,
+      // because otherwise they are swallowed and not reported.
+      System.err.println("Exception when handling " + exchange.getRequestURI() + ": ");
+      e.printStackTrace();
+      throw e;
+    }
+  }
+}
diff --git a/tools/ahat/src/Column.java b/tools/ahat/src/Column.java
new file mode 100644
index 0000000..b7f2829
--- /dev/null
+++ b/tools/ahat/src/Column.java
@@ -0,0 +1,48 @@
+/*
+ * 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;
+
+/**
+ * Configuration of a Doc table column.
+ */
+class Column {
+  public DocString heading;
+  public Align align;
+
+  public static enum Align {
+    LEFT, RIGHT
+  };
+
+  public Column(DocString heading, Align align) {
+    this.heading = heading;
+    this.align = align;
+  }
+
+  /**
+   * Construct a left-aligned column with a simple heading.
+   */
+  public Column(String heading) {
+    this(DocString.text(heading), Align.LEFT);
+  }
+
+  /**
+   * Construct a column with a simple heading.
+   */
+  public Column(String heading, Align align) {
+    this(DocString.text(heading), align);
+  }
+}
diff --git a/tools/ahat/src/Doc.java b/tools/ahat/src/Doc.java
new file mode 100644
index 0000000..7fa70de
--- /dev/null
+++ b/tools/ahat/src/Doc.java
@@ -0,0 +1,97 @@
+/*
+ * 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.util.List;
+
+/**
+ * An interface for rendering a page of content to the user.
+ */
+interface Doc extends AutoCloseable {
+  /**
+   * Output the title of the page.
+   */
+  public void title(String format, Object... args);
+
+  /**
+   * Print a line of text for a page menu.
+   */
+  public void menu(DocString string);
+
+  /**
+   * Start a new section with the given title.
+   */
+  public void section(String title);
+
+  /**
+   * Print a line of text in a normal font.
+   */
+  public void println(DocString string);
+
+  /**
+   * Print a line of text in a large font that is easy to see and click on.
+   */
+  public void big(DocString string);
+
+  /**
+   * Start a table with the given columns.
+   *
+   * An IllegalArgumentException is thrown if no columns are provided.
+   *
+   * This should be followed by calls to the 'row' method to fill in the table
+   * contents and the 'end' method to end the table.
+   */
+  public void table(Column... columns);
+
+  /**
+   * Start a table with the following heading structure:
+   *   |  description  |  c2  | c3 | ... |
+   *   | h1 | h2 | ... |      |    |     |
+   *
+   * Where subcols describes h1, h2, ...
+   * and cols describes c2, c3, ...
+   *
+   * This should be followed by calls to the 'row' method to fill in the table
+   * contents and the 'end' method to end the table.
+   */
+  public void table(DocString description, List<Column> subcols, List<Column> cols);
+
+  /**
+   * Add a row to the currently active table.
+   * The number of values must match the number of columns provided for the
+   * currently active table.
+   */
+  public void row(DocString... values);
+
+  /**
+   * Start a new description list.
+   *
+   * This should be followed by calls to description() and finally a call to
+   * end().
+   */
+  public void descriptions();
+
+  /**
+   * Add a description to the currently active description list.
+   */
+  public void description(DocString key, DocString value);
+
+  /**
+   * End the currently active table or description list.
+   */
+  public void end();
+}
diff --git a/tools/ahat/src/DocString.java b/tools/ahat/src/DocString.java
new file mode 100644
index 0000000..1d997dc
--- /dev/null
+++ b/tools/ahat/src/DocString.java
@@ -0,0 +1,121 @@
+/*
+ * 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.html.HtmlEscapers;
+import java.net.URI;
+import java.net.URISyntaxException;
+
+/**
+ * A class representing a small string of document content consisting of text,
+ * links, images, etc.
+ */
+class DocString {
+  private StringBuilder mStringBuilder;
+
+  public DocString() {
+    mStringBuilder = new StringBuilder();
+  }
+
+  /**
+   * Construct a new DocString, initialized with the given text.
+   * Format arguments are supported.
+   */
+  public static DocString text(String format, Object... args) {
+    DocString doc = new DocString();
+    return doc.append(format, args);
+  }
+
+  /**
+   * Construct a new DocString, initialized with the given link.
+   */
+  public static DocString link(URI uri, DocString content) {
+    DocString doc = new DocString();
+    return doc.appendLink(uri, content);
+
+  }
+
+  /**
+   * Construct a new DocString initialized with the given image.
+   */
+  public static DocString image(URI uri, String alt) {
+    return (new DocString()).appendImage(uri, alt);
+  }
+
+  /**
+   * Append literal text to the given doc string.
+   * Format arguments are supported.
+   * Returns this object.
+   */
+  public DocString append(String format, Object... args) {
+    String text = String.format(format, args);
+    mStringBuilder.append(HtmlEscapers.htmlEscaper().escape(text));
+    return this;
+  }
+
+  public DocString append(DocString str) {
+    mStringBuilder.append(str.html());
+    return this;
+  }
+
+  public DocString appendLink(URI uri, DocString content) {
+    mStringBuilder.append("<a href=\"");
+    mStringBuilder.append(uri.toASCIIString());
+    mStringBuilder.append("\">");
+    mStringBuilder.append(content.html());
+    mStringBuilder.append("</a>");
+    return this;
+  }
+
+  public DocString appendImage(URI uri, String alt) {
+    mStringBuilder.append("<img alt=\"");
+    mStringBuilder.append(HtmlEscapers.htmlEscaper().escape(alt));
+    mStringBuilder.append("\" src=\"");
+    mStringBuilder.append(uri.toASCIIString());
+    mStringBuilder.append("\" />");
+    return this;
+  }
+
+  public DocString appendThumbnail(URI uri, String alt) {
+    mStringBuilder.append("<img height=\"16\" alt=\"");
+    mStringBuilder.append(HtmlEscapers.htmlEscaper().escape(alt));
+    mStringBuilder.append("\" src=\"");
+    mStringBuilder.append(uri.toASCIIString());
+    mStringBuilder.append("\" />");
+    return this;
+  }
+
+  /**
+   * Convenience function for constructing a URI from a string with a uri
+   * known to be valid. Format arguments are supported.
+   */
+  public static URI uri(String format, Object... args) {
+    String uriString = String.format(format, args);
+    try {
+      return new URI(uriString);
+    } catch (URISyntaxException e) {
+      throw new IllegalStateException("Known good uri has syntax error: " + uriString, e);
+    }
+  }
+
+  /**
+   * Render the DocString as html.
+   */
+  public String html() {
+    return mStringBuilder.toString();
+  }
+}
diff --git a/tools/ahat/src/DominatedList.java b/tools/ahat/src/DominatedList.java
new file mode 100644
index 0000000..53d1073
--- /dev/null
+++ b/tools/ahat/src/DominatedList.java
@@ -0,0 +1,165 @@
+/*
+ * 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.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Class for rendering a list of instances dominated by a single instance in a
+ * pretty way.
+ */
+class DominatedList {
+  private static final int kIncrAmount = 100;
+  private static final int kDefaultShown = 100;
+
+  /**
+   * Render a table to the given HtmlWriter showing a pretty list of
+   * instances.
+   *
+   * Rather than show all of the instances (which may be very many), we use
+   * the query parameter "dominated" to specify a limited number of
+   * instances to show. The 'uri' parameter should be the current page URI, so
+   * that we can add links to "show more" and "show less" objects that go to
+   * the same page with only the number of objects adjusted.
+   */
+  public static void render(final AhatSnapshot snapshot, Doc doc,
+      Collection<Instance> instances, Query query) {
+    List<Instance> insts = new ArrayList<Instance>(instances);
+    Collections.sort(insts, Sort.defaultInstanceCompare(snapshot));
+
+    int numInstancesToShow = getNumInstancesToShow(query, insts.size());
+    List<Instance> shown = new ArrayList<Instance>(insts.subList(0, numInstancesToShow));
+    List<Instance> hidden = insts.subList(numInstancesToShow, insts.size());
+
+    // Add 'null' as a marker for "all the rest of the objects".
+    if (!hidden.isEmpty()) {
+      shown.add(null);
+    }
+    HeapTable.render(doc, new TableConfig(snapshot, hidden), snapshot, shown);
+
+    if (insts.size() > kDefaultShown) {
+      printMenu(doc, query, numInstancesToShow, insts.size());
+    }
+  }
+
+  private static class TableConfig implements HeapTable.TableConfig<Instance> {
+    AhatSnapshot mSnapshot;
+
+    // Map from heap name to the total size of the instances not shown in the
+    // table.
+    Map<Heap, Long> mHiddenSizes;
+
+    public TableConfig(AhatSnapshot snapshot, List<Instance> hidden) {
+      mSnapshot = snapshot;
+      mHiddenSizes = new HashMap<Heap, Long>();
+      for (Heap heap : snapshot.getHeaps()) {
+        mHiddenSizes.put(heap, 0L);
+      }
+
+      if (!hidden.isEmpty()) {
+        for (Instance inst : hidden) {
+          for (Heap heap : snapshot.getHeaps()) {
+            int index = snapshot.getHeapIndex(heap);
+            long size = inst.getRetainedSize(index);
+            mHiddenSizes.put(heap, mHiddenSizes.get(heap) + size);
+          }
+        }
+      }
+    }
+
+    @Override
+    public String getHeapsDescription() {
+      return "Bytes Retained by Heap";
+    }
+
+    @Override
+    public long getSize(Instance element, Heap heap) {
+      if (element == null) {
+        return mHiddenSizes.get(heap);
+      }
+      int index = mSnapshot.getHeapIndex(heap);
+      return element.getRetainedSize(index);
+    }
+
+    @Override
+    public List<HeapTable.ValueConfig<Instance>> getValueConfigs() {
+      HeapTable.ValueConfig<Instance> value = new HeapTable.ValueConfig<Instance>() {
+        public String getDescription() {
+          return "Object";
+        }
+
+        public DocString render(Instance element) {
+          if (element == null) {
+            return DocString.text("...");
+          } else {
+            return Value.render(element);
+          }
+        }
+      };
+      return Collections.singletonList(value);
+    }
+  }
+
+  // Figure out how many objects to show based on the query parameter.
+  // The resulting value is guaranteed to be at least zero, and no greater
+  // than the number of total objects.
+  private static int getNumInstancesToShow(Query query, int totalNumInstances) {
+    String value = query.get("dominated", null);
+    try {
+      int count = Math.min(totalNumInstances, Integer.parseInt(value));
+      return Math.max(0, count);
+    } catch (NumberFormatException e) {
+      // We can't parse the value as a number. Ignore it.
+    }
+    return Math.min(kDefaultShown, totalNumInstances);
+  }
+
+  // Print a menu line after the table to control how many objects are shown.
+  // It has the form:
+  //  (showing X of Y objects - show none - show less - show more - show all)
+  private static void printMenu(Doc doc, Query query, int shown, int all) {
+    DocString menu = new DocString();
+    menu.append("(%d of %d objects shown - ", shown, all);
+    if (shown > 0) {
+      int less = Math.max(0, shown - kIncrAmount);
+      menu.appendLink(query.with("dominated", 0), DocString.text("show none"));
+      menu.append(" - ");
+      menu.appendLink(query.with("dominated", less), DocString.text("show less"));
+      menu.append(" - ");
+    } else {
+      menu.append("show none - show less - ");
+    }
+    if (shown < all) {
+      int more = Math.min(shown + kIncrAmount, all);
+      menu.appendLink(query.with("dominated", more), DocString.text("show more"));
+      menu.append(" - ");
+      menu.appendLink(query.with("dominated", all), DocString.text("show all"));
+      menu.append(")");
+    } else {
+      menu.append("show more - show all)");
+    }
+    doc.println(menu);
+  }
+}
+
diff --git a/tools/ahat/src/HeapTable.java b/tools/ahat/src/HeapTable.java
new file mode 100644
index 0000000..60bb387
--- /dev/null
+++ b/tools/ahat/src/HeapTable.java
@@ -0,0 +1,106 @@
+/*
+ * 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 java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Class for rendering a table that includes sizes of some kind for each heap.
+ */
+class HeapTable {
+  /**
+   * Configuration for a value column of a heap table.
+   */
+  public static interface ValueConfig<T> {
+    public String getDescription();
+    public DocString render(T element);
+  }
+
+  /**
+   * Configuration for the HeapTable.
+   */
+  public static interface TableConfig<T> {
+    public String getHeapsDescription();
+    public long getSize(T element, Heap heap);
+    public List<ValueConfig<T>> getValueConfigs();
+  }
+
+  public static <T> void render(Doc doc, 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)) {
+        heaps.add(heap);
+      }
+    }
+
+    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) {
+      subcols.add(new Column(heap.getName(), Column.Align.RIGHT));
+    }
+    if (showTotal) {
+      subcols.add(new Column("Total", Column.Align.RIGHT));
+    }
+    List<Column> cols = new ArrayList<Column>();
+    for (ValueConfig value : values) {
+      cols.add(new Column(value.getDescription()));
+    }
+    doc.table(DocString.text(config.getHeapsDescription()), subcols, cols);
+
+    // Print the entries.
+    ArrayList<DocString> vals = new ArrayList<DocString>();
+    for (T elem : elements) {
+      vals.clear();
+      long total = 0;
+      for (Heap heap : heaps) {
+        long size = config.getSize(elem, heap);
+        total += size;
+        vals.add(DocString.text("%,14d", size));
+      }
+      if (showTotal) {
+        vals.add(DocString.text("%,14d", total));
+      }
+
+      for (ValueConfig<T> value : values) {
+        vals.add(value.render(elem));
+      }
+      doc.row(vals.toArray(new DocString[0]));
+    }
+    doc.end();
+  }
+
+  // Returns true if the given heap has a non-zero size entry.
+  public static <T> boolean hasNonZeroEntry(AhatSnapshot snapshot, Heap heap,
+      TableConfig<T> config, List<T> elements) {
+    if (snapshot.getHeapSize(heap) > 0) {
+      for (T element : elements) {
+        if (config.getSize(element, heap) > 0) {
+          return true;
+        }
+      }
+    }
+    return false;
+  }
+}
+
diff --git a/tools/ahat/src/HtmlDoc.java b/tools/ahat/src/HtmlDoc.java
new file mode 100644
index 0000000..5ccbacb
--- /dev/null
+++ b/tools/ahat/src/HtmlDoc.java
@@ -0,0 +1,175 @@
+/*
+ * 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.PrintStream;
+import java.net.URI;
+import java.util.List;
+
+/**
+ * An Html implementation of Doc.
+ */
+public class HtmlDoc implements Doc {
+  private PrintStream ps;
+  private Column[] mCurrentTableColumns;
+
+  /**
+   * Create an HtmlDoc that writes to the given print stream.
+   * @param title - The main page title.
+   * @param style - A URI link to a stylesheet to link to.
+   */
+  public HtmlDoc(PrintStream ps, DocString title, URI style) {
+    this.ps = ps;
+
+    ps.println("<!DOCTYPE html>");
+    ps.println("<html>");
+    ps.println("<head>");
+    ps.format("<title>%s</title>\n", title.html());
+    ps.format("<link rel=\"stylesheet\" type=\"text/css\" href=\"%s\">\n",
+        style.toASCIIString());
+    ps.println("</head>");
+    ps.println("<body>");
+  }
+
+  @Override
+  public void title(String format, Object... args) {
+    ps.print("<h1>");
+    ps.print(DocString.text(String.format(format, args)).html());
+    ps.println("</h1>");
+  }
+
+  @Override
+  public void menu(DocString string) {
+    ps.format("<div class=\"menu\">%s</div>", string.html());
+  }
+
+  @Override
+  public void section(String title) {
+    ps.print("<h2>");
+    ps.print(DocString.text(title).html());
+    ps.println(":</h2>");
+  }
+
+  @Override
+  public void println(DocString string) {
+    ps.print(string.html());
+    ps.println("<br />");
+  }
+
+  @Override
+  public void big(DocString str) {
+    ps.print("<h2>");
+    ps.print(str.html());
+    ps.println("</h2>");
+  }
+
+  @Override
+  public void table(Column... columns) {
+    if (columns.length == 0) {
+      throw new IllegalArgumentException("No columns specified");
+    }
+
+    mCurrentTableColumns = columns;
+    ps.println("<table>");
+    for (int i = 0; i < columns.length - 1; i++) {
+      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());
+  }
+
+  @Override
+  public void table(DocString description, List<Column> subcols, List<Column> cols) {
+    mCurrentTableColumns = new Column[subcols.size() + cols.size()];
+    int j = 0;
+    for (Column col : subcols) {
+      mCurrentTableColumns[j] = col;
+      j++;
+    }
+    for (Column col : cols) {
+      mCurrentTableColumns[j] = col;
+      j++;
+    }
+
+    ps.println("<table>");
+    ps.format("<tr><th colspan=\"%d\">%s</th>", subcols.size(), 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.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());
+    }
+    ps.println("</tr>");
+
+    ps.print("<tr>");
+    for (Column subcol : subcols) {
+      ps.format("<th>%s</th>", subcol.heading.html());
+    }
+    ps.println("</tr>");
+  }
+
+  @Override
+  public void row(DocString... values) {
+    if (mCurrentTableColumns == null) {
+      throw new IllegalStateException("table method must be called before row");
+    }
+
+    if (mCurrentTableColumns.length != values.length) {
+      throw new IllegalArgumentException(String.format(
+          "Wrong number of row values. Expected %d, but got %d",
+          mCurrentTableColumns.length, values.length));
+    }
+
+    ps.print("<tr>");
+    for (int i = 0; i < values.length; i++) {
+      ps.print("<td");
+      if (mCurrentTableColumns[i].align == Column.Align.RIGHT) {
+        ps.print(" align=\"right\"");
+      }
+      ps.format(">%s</td>", values[i].html());
+    }
+    ps.println("</tr>");
+  }
+
+  @Override
+  public void descriptions() {
+    ps.println("<table>");
+  }
+
+  @Override
+  public void description(DocString key, DocString value) {
+    ps.format("<tr><th align=\"left\">%s:</th><td>%s</td></tr>", key.html(), value.html());
+  }
+
+  @Override
+  public void end() {
+    ps.println("</table>");
+    mCurrentTableColumns = null;
+  }
+
+  @Override
+  public void close() {
+    ps.println("</body>");
+    ps.println("</html>");
+    ps.close();
+  }
+}
diff --git a/tools/ahat/src/InstanceUtils.java b/tools/ahat/src/InstanceUtils.java
new file mode 100644
index 0000000..7ee3ff2
--- /dev/null
+++ b/tools/ahat/src/InstanceUtils.java
@@ -0,0 +1,238 @@
+/*
+ * 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.Instance;
+import com.android.tools.perflib.heap.Type;
+import java.awt.image.BufferedImage;
+
+/**
+ * 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.
+   */
+  public static boolean isInstanceOfClass(Instance inst, String className) {
+    ClassObj cls = inst.getClassObj();
+    return (cls != null && className.equals(cls.getClassName()));
+  }
+
+  /**
+   * Read the char[] value from an hprof Instance.
+   * Returns null if the object can't be interpreted as a char[].
+   */
+  private static char[] asCharArray(Instance inst) {
+    if (! (inst instanceof ArrayInstance)) {
+      return null;
+    }
+
+    ArrayInstance array = (ArrayInstance) inst;
+    if (array.getArrayType() != Type.CHAR) {
+      return null;
+    }
+    return array.asCharArray(0, array.getValues().length);
+  }
+
+  /**
+   * 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) {
+    if (!isInstanceOfClass(inst, "java.lang.String")) {
+      return null;
+    }
+    char[] value = getCharArrayField(inst, "value");
+    return (value == null) ? null : new String(value);
+  }
+
+  /**
+   * 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");
+    if (width == null) {
+      return null;
+    }
+
+    Integer height = getIntField(inst, "mHeight");
+    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.
+   */
+  private 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.
+   */
+  private 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 null if the field value is not an int or could not be read.
+   */
+  private static Integer getIntField(Instance inst, String fieldName) {
+    Object value = getField(inst, fieldName);
+    if (!(value instanceof Integer)) {
+      return null;
+    }
+    return (Integer)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);
+  }
+
+  private static char[] getCharArrayField(Instance inst, String fieldName) {
+    Object value = getField(inst, fieldName);
+    if (!(value instanceof Instance)) {
+      return null;
+    }
+    return asCharArray((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.getHardReferences().size() == 1) {
+        Instance ref = inst.getHardReferences().get(0);
+        ClassObj clsref = ref.getClassObj();
+        if (clsref != null && "android.graphics.Bitmap".equals(clsref.getClassName())) {
+          return ref;
+        }
+      }
+    }
+    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.
+   */
+  public static String getDexCacheLocation(Instance inst) {
+    if (isInstanceOfClass(inst, "java.lang.DexCache")) {
+      Instance location = getRefField(inst, "location");
+      if (location != null) {
+        return asString(location);
+      }
+    }
+    return null;
+  }
+}
diff --git a/tools/ahat/src/Main.java b/tools/ahat/src/Main.java
new file mode 100644
index 0000000..2e2ddd2
--- /dev/null
+++ b/tools/ahat/src/Main.java
@@ -0,0 +1,100 @@
+/*
+ * 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.HprofParser;
+import com.android.tools.perflib.heap.Snapshot;
+import com.android.tools.perflib.heap.io.HprofBuffer;
+import com.android.tools.perflib.heap.io.MemoryMappedFileBuffer;
+import com.sun.net.httpserver.HttpServer;
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.util.concurrent.Executors;
+
+public class Main {
+
+  public static void help(PrintStream out) {
+    out.println("java -jar ahat.jar [-p port] FILE");
+    out.println("  Launch an http server for viewing "
+        + "the given Android heap-dump FILE.");
+    out.println("");
+    out.println("Options:");
+    out.println("  -p <port>");
+    out.println("     Serve pages on the given port. Defaults to 7100.");
+    out.println("");
+  }
+
+  public static void main(String[] args) throws IOException {
+    int port = 7100;
+    for (String arg : args) {
+      if (arg.equals("--help")) {
+        help(System.out);
+        return;
+      }
+    }
+
+    File hprof = null;
+    for (int i = 0; i < args.length; i++) {
+      if ("-p".equals(args[i]) && i + 1 < args.length) {
+        i++;
+        port = Integer.parseInt(args[i]);
+      } else {
+        if (hprof != null) {
+          System.err.println("multiple input files.");
+          help(System.err);
+          return;
+        }
+        hprof = new File(args[i]);
+      }
+    }
+
+    if (hprof == null) {
+      System.err.println("no input file.");
+      help(System.err);
+      return;
+    }
+
+    System.out.println("Reading hprof file...");
+    HprofBuffer buffer = new MemoryMappedFileBuffer(hprof);
+    Snapshot snapshot = (new HprofParser(buffer)).parse();
+
+    System.out.println("Computing Dominators...");
+    snapshot.computeDominators();
+
+    System.out.println("Processing snapshot for ahat...");
+    AhatSnapshot ahat = new AhatSnapshot(snapshot);
+
+    InetAddress loopback = InetAddress.getLoopbackAddress();
+    InetSocketAddress addr = new InetSocketAddress(loopback, port);
+    HttpServer server = HttpServer.create(addr, 0);
+    server.createContext("/", new OverviewHandler(ahat, hprof));
+    server.createContext("/roots", new RootsHandler(ahat));
+    server.createContext("/object", new ObjectHandler(ahat));
+    server.createContext("/objects", new ObjectsHandler(ahat));
+    server.createContext("/site", new SiteHandler(ahat));
+    server.createContext("/bitmap", new BitmapHandler(ahat));
+    server.createContext("/help", new StaticHandler("help.html", "text/html"));
+    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/ObjectHandler.java b/tools/ahat/src/ObjectHandler.java
new file mode 100644
index 0000000..eecd7d1
--- /dev/null
+++ b/tools/ahat/src/ObjectHandler.java
@@ -0,0 +1,209 @@
+/*
+ * 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 java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+class ObjectHandler extends AhatHandler {
+  public ObjectHandler(AhatSnapshot snapshot) {
+    super(snapshot);
+  }
+
+  @Override
+  public void handle(Doc doc, Query query) throws IOException {
+    long id = query.getLong("id", 0);
+    Instance inst = mSnapshot.findInstance(id);
+    if (inst == null) {
+      doc.println(DocString.text("No object with id %08xl", id));
+      return;
+    }
+
+    doc.title("Object %08x", inst.getUniqueId());
+    doc.big(Value.render(inst));
+
+    printAllocationSite(doc, inst);
+    printDominatorPath(doc, inst);
+
+    doc.section("Object Info");
+    ClassObj cls = inst.getClassObj();
+    doc.descriptions();
+    doc.description(DocString.text("Class"), Value.render(cls));
+    doc.description(DocString.text("Size"), DocString.text("%d", inst.getSize()));
+    doc.description(
+        DocString.text("Retained Size"),
+        DocString.text("%d", inst.getTotalRetainedSize()));
+    doc.description(DocString.text("Heap"), DocString.text(inst.getHeap().getName()));
+    doc.end();
+
+    printBitmap(doc, inst);
+    if (inst instanceof ClassInstance) {
+      printClassInstanceFields(doc, (ClassInstance)inst);
+    } else if (inst instanceof ArrayInstance) {
+      printArrayElements(doc, (ArrayInstance)inst);
+    } else if (inst instanceof ClassObj) {
+      printClassInfo(doc, (ClassObj)inst);
+    }
+    printReferences(doc, inst);
+    printDominatedObjects(doc, query, inst);
+  }
+
+  private static void printClassInstanceFields(Doc doc, ClassInstance inst) {
+    doc.section("Fields");
+    doc.table(new Column("Type"), new Column("Name"), new Column("Value"));
+    for (ClassInstance.FieldValue field : inst.getValues()) {
+      doc.row(
+          DocString.text(field.getField().getType().toString()),
+          DocString.text(field.getField().getName()),
+          Value.render(field.getValue()));
+    }
+    doc.end();
+  }
+
+  private static void printArrayElements(Doc doc, ArrayInstance array) {
+    doc.section("Array Elements");
+    doc.table(new Column("Index", Column.Align.RIGHT), new Column("Value"));
+    Object[] elements = array.getValues();
+    for (int i = 0; i < elements.length; i++) {
+      doc.row(DocString.text("%d", i), Value.render(elements[i]));
+    }
+    doc.end();
+  }
+
+  private static void printClassInfo(Doc doc, ClassObj clsobj) {
+    doc.section("Class Info");
+    doc.descriptions();
+    doc.description(DocString.text("Super Class"), Value.render(clsobj.getSuperClassObj()));
+    doc.description(DocString.text("Class Loader"), Value.render(clsobj.getClassLoader()));
+    doc.end();
+
+    doc.section("Static Fields");
+    doc.table(new Column("Type"), new Column("Name"), new Column("Value"));
+    for (Map.Entry<Field, Object> field : clsobj.getStaticFieldValues().entrySet()) {
+      doc.row(
+          DocString.text(field.getKey().getType().toString()),
+          DocString.text(field.getKey().getName()),
+          Value.render(field.getValue()));
+    }
+    doc.end();
+  }
+
+  private static void printReferences(Doc doc, Instance inst) {
+    doc.section("Objects with References to this Object");
+    if (inst.getHardReferences().isEmpty()) {
+      doc.println(DocString.text("(none)"));
+    } else {
+      doc.table(new Column("Object"));
+      for (Instance ref : inst.getHardReferences()) {
+        doc.row(Value.render(ref));
+      }
+      doc.end();
+    }
+
+    if (inst.getSoftReferences() != null) {
+      doc.section("Objects with Soft References to this Object");
+      doc.table(new Column("Object"));
+      for (Instance ref : inst.getSoftReferences()) {
+        doc.row(Value.render(inst));
+      }
+      doc.end();
+    }
+  }
+
+  private void printAllocationSite(Doc doc, Instance inst) {
+    doc.section("Allocation Site");
+    Site site = mSnapshot.getSiteForInstance(inst);
+    SitePrinter.printSite(doc, mSnapshot, 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);
+    if (bitmap != null) {
+      doc.section("Bitmap Image");
+      doc.println(DocString.image(
+            DocString.uri("bitmap?id=%d", bitmap.getId()), "bitmap image"));
+    }
+  }
+
+  private void printDominatorPath(Doc doc, Instance inst) {
+    doc.section("Dominator Path from Root");
+    List<Instance> path = new ArrayList<Instance>();
+    for (Instance parent = inst;
+        parent != null && !(parent instanceof RootObj);
+        parent = parent.getImmediateDominator()) {
+      path.add(parent);
+    }
+
+    // Add 'null' as a marker for the root.
+    path.add(null);
+    Collections.reverse(path);
+
+    HeapTable.TableConfig<Instance> table = new HeapTable.TableConfig<Instance>() {
+      public String getHeapsDescription() {
+        return "Bytes Retained by Heap";
+      }
+
+      public long getSize(Instance element, Heap heap) {
+        if (element == null) {
+          return mSnapshot.getHeapSize(heap);
+        }
+        int index = mSnapshot.getHeapIndex(heap);
+        return element.getRetainedSize(index);
+      }
+
+      public List<HeapTable.ValueConfig<Instance>> getValueConfigs() {
+        HeapTable.ValueConfig<Instance> value = new HeapTable.ValueConfig<Instance>() {
+          public String getDescription() {
+            return "Object";
+          }
+
+          public DocString render(Instance element) {
+            if (element == null) {
+              return DocString.link(DocString.uri("roots"), DocString.text("ROOT"));
+            } else {
+              return DocString.text("→ ").append(Value.render(element));
+            }
+          }
+        };
+        return Collections.singletonList(value);
+      }
+    };
+    HeapTable.render(doc, table, mSnapshot, path);
+  }
+
+  public void printDominatedObjects(Doc doc, Query query, Instance inst) {
+    doc.section("Immediately Dominated Objects");
+    List<Instance> instances = mSnapshot.getDominated(inst);
+    if (instances != null) {
+      DominatedList.render(mSnapshot, doc, instances, query);
+    } else {
+      doc.println(DocString.text("(none)"));
+    }
+  }
+}
+
diff --git a/tools/ahat/src/ObjectsHandler.java b/tools/ahat/src/ObjectsHandler.java
new file mode 100644
index 0000000..066c9d5
--- /dev/null
+++ b/tools/ahat/src/ObjectsHandler.java
@@ -0,0 +1,63 @@
+/*
+ * 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.Instance;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+class ObjectsHandler extends AhatHandler {
+  public ObjectsHandler(AhatSnapshot snapshot) {
+    super(snapshot);
+  }
+
+  @Override
+  public void handle(Doc doc, Query query) throws IOException {
+    int stackId = query.getInt("stack", 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);
+
+    List<Instance> insts = new ArrayList<Instance>();
+    for (Instance inst : site.getObjects()) {
+      if ((heapName == null || inst.getHeap().getName().equals(heapName))
+          && (className == null
+            || AhatSnapshot.getClassName(inst.getClassObj()).equals(className))) {
+        insts.add(inst);
+      }
+    }
+
+    Collections.sort(insts, Sort.defaultInstanceCompare(mSnapshot));
+
+    doc.title("Objects");
+    doc.table(
+        new Column("Size", Column.Align.RIGHT),
+        new Column("Heap"),
+        new Column("Object"));
+    for (Instance inst : insts) {
+      doc.row(
+          DocString.text("%,d", inst.getSize()),
+          DocString.text(inst.getHeap().getName()),
+          Value.render(inst));
+    }
+    doc.end();
+  }
+}
+
diff --git a/tools/ahat/src/OverviewHandler.java b/tools/ahat/src/OverviewHandler.java
new file mode 100644
index 0000000..6e6c323
--- /dev/null
+++ b/tools/ahat/src/OverviewHandler.java
@@ -0,0 +1,76 @@
+/*
+ * 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 java.io.IOException;
+import java.io.File;
+import java.util.Collections;
+import java.util.List;
+
+class OverviewHandler extends AhatHandler {
+  private File mHprof;
+
+  public OverviewHandler(AhatSnapshot snapshot, File hprof) {
+    super(snapshot);
+    mHprof = hprof;
+  }
+
+  @Override
+  public void handle(Doc doc, Query query) throws IOException {
+    doc.title("Overview");
+
+    doc.section("General Information");
+    doc.descriptions();
+    doc.description(
+        DocString.text("ahat version"),
+        DocString.text("ahat-%s", OverviewHandler.class.getPackage().getImplementationVersion()));
+    doc.description(DocString.text("hprof file"), DocString.text(mHprof.toString()));
+    doc.end();
+
+    doc.section("Heap Sizes");
+    printHeapSizes(doc);
+
+    DocString menu = new DocString();
+    menu.appendLink(DocString.uri("roots"), DocString.text("Roots"));
+    menu.append(" - ");
+    menu.appendLink(DocString.uri("site"), DocString.text("Allocations"));
+    menu.append(" - ");
+    menu.appendLink(DocString.uri("help"), DocString.text("Help"));
+    doc.big(menu);
+  }
+
+  private void printHeapSizes(Doc doc) {
+    List<Object> dummy = Collections.singletonList(null);
+
+    HeapTable.TableConfig<Object> table = new HeapTable.TableConfig<Object>() {
+      public String getHeapsDescription() {
+        return "Bytes Retained by Heap";
+      }
+
+      public long getSize(Object element, Heap heap) {
+        return mSnapshot.getHeapSize(heap);
+      }
+
+      public List<HeapTable.ValueConfig<Object>> getValueConfigs() {
+        return Collections.emptyList();
+      }
+    };
+    HeapTable.render(doc, table, mSnapshot, dummy);
+  }
+}
+
diff --git a/tools/ahat/src/Query.java b/tools/ahat/src/Query.java
new file mode 100644
index 0000000..f910608
--- /dev/null
+++ b/tools/ahat/src/Query.java
@@ -0,0 +1,111 @@
+/*
+ * 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.net.URI;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.TreeMap;
+
+/**
+ * A class for getting and modifying query parameters.
+ */
+class Query {
+  private URI mUri;
+
+  // Map from parameter name to value. If the same parameter appears multiple
+  // times, only the last value is used.
+  private Map<String, String> mParams;
+
+  public Query(URI uri) {
+    mUri = uri;
+    mParams = new HashMap<String, String>();
+
+    String query = uri.getQuery();
+    if (query != null) {
+      for (String param : query.split("&")) {
+        int i = param.indexOf('=');
+        if (i < 0) {
+          mParams.put(param, "");
+        } else {
+          mParams.put(param.substring(0, i), param.substring(i + 1));
+        }
+      }
+    }
+  }
+
+  /**
+   * Return the value of a query parameter with the given name.
+   * If there is no query parameter with that name, returns the default value.
+   * If there are multiple query parameters with that name, the value of the
+   * last query parameter is returned.
+   * If the parameter is defined with an empty value, "" is returned.
+   */
+  public String get(String name, String defaultValue) {
+    String value = mParams.get(name);
+    return (value == null) ? defaultValue : value;
+  }
+
+  /**
+   * Return the long value of a query parameter with the given name.
+   */
+  public long getLong(String name, long defaultValue) {
+    String value = get(name, null);
+    return value == null ? defaultValue : Long.parseLong(value);
+  }
+
+  /**
+   * Return the int value of a query parameter with the given name.
+   */
+  public int getInt(String name, int defaultValue) {
+    String value = get(name, null);
+    return value == null ? defaultValue : Integer.parseInt(value);
+  }
+
+  /**
+   * Return a uri suitable for an href target that links to the current
+   * page, except with the named query parameter set to the new value.
+   *
+   * The generated parameters will be sorted alphabetically so it is easier to
+   * test.
+   */
+  public URI with(String name, String value) {
+    StringBuilder newQuery = new StringBuilder();
+    newQuery.append(mUri.getRawPath());
+    newQuery.append('?');
+
+    Map<String, String> params = new TreeMap<String, String>(mParams);
+    params.put(name, value);
+    String and = "";
+    for (Map.Entry<String, String> entry : params.entrySet()) {
+      newQuery.append(and);
+      newQuery.append(entry.getKey());
+      newQuery.append('=');
+      newQuery.append(entry.getValue());
+      and = "&";
+    }
+    return DocString.uri(newQuery.toString());
+  }
+
+  /**
+   * Return a uri suitable for an href target that links to the current
+   * page, except with the named query parameter set to the new long value.
+   */
+  public URI with(String name, long value) {
+    return with(name, String.valueOf(value));
+  }
+}
diff --git a/tools/ahat/src/RootsHandler.java b/tools/ahat/src/RootsHandler.java
new file mode 100644
index 0000000..185b9bf
--- /dev/null
+++ b/tools/ahat/src/RootsHandler.java
@@ -0,0 +1,51 @@
+/*
+ * 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.Instance;
+import com.android.tools.perflib.heap.RootObj;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+class RootsHandler extends AhatHandler {
+  public RootsHandler(AhatSnapshot snapshot) {
+    super(snapshot);
+  }
+
+  @Override
+  public void handle(Doc doc, Query query) throws IOException {
+    doc.title("Roots");
+
+    Set<Instance> rootset = new HashSet<Instance>();
+    for (RootObj root : mSnapshot.getGCRoots()) {
+      Instance inst = root.getReferredInstance();
+      if (inst != null) {
+        rootset.add(inst);
+      }
+    }
+
+    List<Instance> roots = new ArrayList<Instance>();
+    for (Instance inst : rootset) {
+      roots.add(inst);
+    }
+    DominatedList.render(mSnapshot, doc, roots, query);
+  }
+}
+
diff --git a/tools/ahat/src/Site.java b/tools/ahat/src/Site.java
new file mode 100644
index 0000000..d504096
--- /dev/null
+++ b/tools/ahat/src/Site.java
@@ -0,0 +1,198 @@
+/*
+ * 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
new file mode 100644
index 0000000..8fbc176
--- /dev/null
+++ b/tools/ahat/src/SiteHandler.java
@@ -0,0 +1,101 @@
+/*
+ * 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 java.io.IOException;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+class SiteHandler extends AhatHandler {
+  public SiteHandler(AhatSnapshot snapshot) {
+    super(snapshot);
+  }
+
+  @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);
+
+    doc.title("Site %s", site.getName());
+    doc.section("Allocation Site");
+    SitePrinter.printSite(doc, mSnapshot, site);
+
+    doc.section("Sites Called from Here");
+    List<Site> children = site.getChildren();
+    if (children.isEmpty()) {
+      doc.println(DocString.text("(none)"));
+    } else {
+      Collections.sort(children, new Sort.SiteBySize("app"));
+
+      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 List<HeapTable.ValueConfig<Site>> getValueConfigs() {
+          HeapTable.ValueConfig<Site> value = new HeapTable.ValueConfig<Site>() {
+            public String getDescription() {
+              return "Child Site";
+            }
+
+            public DocString render(Site element) {
+              return DocString.link(
+                  DocString.uri("site?stack=%d&depth=%d",
+                    element.getStackId(), element.getStackDepth()),
+                  DocString.text(element.getName()));
+            }
+          };
+          return Collections.singletonList(value);
+        }
+      };
+      HeapTable.render(doc, table, mSnapshot, children);
+    }
+
+    doc.section("Objects Allocated");
+    doc.table(
+        new Column("Reachable Bytes Allocated", Column.Align.RIGHT),
+        new Column("Instances", Column.Align.RIGHT),
+        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());
+    Collections.sort(infos, compare);
+    for (Site.ObjectsInfo info : infos) {
+      String className = AhatSnapshot.getClassName(info.classObj);
+      doc.row(
+          DocString.text("%,14d", info.numBytes),
+          DocString.link(
+            DocString.uri("objects?stack=%d&depth=%d&heap=%s&class=%s",
+                site.getStackId(), site.getStackDepth(), info.heap.getName(), className),
+            DocString.text("%,14d", info.numInstances)),
+          DocString.text(info.heap.getName()),
+          Value.render(info.classObj));
+    }
+    doc.end();
+  }
+}
+
diff --git a/tools/ahat/src/SitePrinter.java b/tools/ahat/src/SitePrinter.java
new file mode 100644
index 0000000..9c0c2e0
--- /dev/null
+++ b/tools/ahat/src/SitePrinter.java
@@ -0,0 +1,65 @@
+/*
+ * 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 java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+class SitePrinter {
+  public static void printSite(Doc doc, AhatSnapshot snapshot, Site site) {
+    List<Site> path = new ArrayList<Site>();
+    for (Site parent = site; parent != null; parent = parent.getParent()) {
+      path.add(parent);
+    }
+    Collections.reverse(path);
+
+
+    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 List<HeapTable.ValueConfig<Site>> getValueConfigs() {
+        HeapTable.ValueConfig<Site> value = new HeapTable.ValueConfig<Site>() {
+          public String getDescription() {
+            return "Stack Frame";
+          }
+
+          public DocString render(Site element) {
+            DocString str = new DocString();
+            if (element.getParent() != null) {
+              str.append("→ ");
+            }
+            str.appendLink(
+                DocString.uri("site?stack=%d&depth=%d",
+                    element.getStackId(), element.getStackDepth()),
+                DocString.text(element.getName()));
+            return str;
+          }
+        };
+        return Collections.singletonList(value);
+      }
+    };
+    HeapTable.render(doc, table, snapshot, path);
+  }
+}
diff --git a/tools/ahat/src/Sort.java b/tools/ahat/src/Sort.java
new file mode 100644
index 0000000..3b79166
--- /dev/null
+++ b/tools/ahat/src/Sort.java
@@ -0,0 +1,181 @@
+/*
+ * 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.Instance;
+import com.android.tools.perflib.heap.Heap;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Iterator;
+
+/**
+ * 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);
+    }
+  }
+}
+
diff --git a/tools/ahat/src/StaticHandler.java b/tools/ahat/src/StaticHandler.java
new file mode 100644
index 0000000..fb7049d
--- /dev/null
+++ b/tools/ahat/src/StaticHandler.java
@@ -0,0 +1,56 @@
+/*
+ * 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.HttpHandler;
+import com.sun.net.httpserver.HttpExchange;
+import java.io.InputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintStream;
+
+// Handler that returns a static file included in ahat.jar.
+class StaticHandler implements HttpHandler {
+  private String mResourceName;
+  private String mContentType;
+
+  public StaticHandler(String resourceName, String contentType) {
+    mResourceName = resourceName;
+    mContentType = contentType;
+  }
+
+  @Override
+  public void handle(HttpExchange exchange) throws IOException {
+    ClassLoader loader = StaticHandler.class.getClassLoader();
+    InputStream is = loader.getResourceAsStream(mResourceName);
+    if (is == null) {
+      exchange.getResponseHeaders().add("Content-Type", "text/html");
+      exchange.sendResponseHeaders(404, 0);
+      PrintStream ps = new PrintStream(exchange.getResponseBody());
+      HtmlDoc doc = new HtmlDoc(ps, DocString.text("ahat"), DocString.uri("style.css"));
+      doc.big(DocString.text("Resource not found."));
+      doc.close();
+    } else {
+      exchange.getResponseHeaders().add("Content-Type", mContentType);
+      exchange.sendResponseHeaders(200, 0);
+      OutputStream os = exchange.getResponseBody();
+      ByteStreams.copy(is, os);
+      os.close();
+    }
+  }
+}
diff --git a/tools/ahat/src/Value.java b/tools/ahat/src/Value.java
new file mode 100644
index 0000000..22c3b8f
--- /dev/null
+++ b/tools/ahat/src/Value.java
@@ -0,0 +1,80 @@
+/*
+ * 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 {
+
+  /**
+   * Create a DocString representing a summary of the given instance.
+   */
+  private static DocString renderInstance(Instance inst) {
+    DocString link = new DocString();
+    if (inst == null) {
+      link.append("(null)");
+      return link;
+    }
+
+    // Annotate classes as classes.
+    if (inst instanceof ClassObj) {
+      link.append("class ");
+    }
+
+    link.append(inst.toString());
+
+    // Annotate Strings with their values.
+    String stringValue = InstanceUtils.asString(inst);
+    if (stringValue != null) {
+      link.append("\"%s\"", stringValue);
+    }
+
+    // Annotate DexCache with its location.
+    String dexCacheLocation = InstanceUtils.getDexCacheLocation(inst);
+    if (dexCacheLocation != null) {
+      link.append(" for " + dexCacheLocation);
+    }
+
+    URI objTarget = DocString.uri("object?id=%d", inst.getId());
+    DocString formatted = DocString.link(objTarget, link);
+
+    // Annotate bitmaps with a thumbnail.
+    Instance bitmap = InstanceUtils.getAssociatedBitmapInstance(inst);
+    String thumbnail = "";
+    if (bitmap != null) {
+      URI uri = DocString.uri("bitmap?id=%d", bitmap.getId());
+      formatted.appendThumbnail(uri, "bitmap image");
+    }
+    return formatted;
+  }
+
+  /**
+   * Create a DocString summarizing the given value.
+   */
+  public static DocString render(Object val) {
+    if (val instanceof Instance) {
+      return renderInstance((Instance)val);
+    } else {
+      return DocString.text("%s", val);
+    }
+  }
+}
diff --git a/tools/ahat/src/help.html b/tools/ahat/src/help.html
new file mode 100644
index 0000000..b48d791
--- /dev/null
+++ b/tools/ahat/src/help.html
@@ -0,0 +1,56 @@
+<!--
+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.
+-->
+
+<head>
+<link rel="stylesheet" type="text/css" href="style.css">
+</head>
+
+<div class="menu">
+  <a href="/">overview</a> -
+  <a href="roots">roots</a> -
+  <a href="sites">allocations</a> -
+  <a href="help">help</a>
+</div>
+
+<h1>Help</h1>
+<h2>Information shown by ahat:</h2>
+<ul>
+  <li><a href="/">The total bytes retained by heap.</a></li>
+  <li><a href="/roots">A list of root 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>
diff --git a/tools/ahat/src/manifest.txt b/tools/ahat/src/manifest.txt
new file mode 100644
index 0000000..7efb1a7
--- /dev/null
+++ b/tools/ahat/src/manifest.txt
@@ -0,0 +1,4 @@
+Name: ahat/
+Implementation-Title: ahat
+Implementation-Version: 0.2
+Main-Class: com.android.ahat.Main
diff --git a/tools/ahat/src/style.css b/tools/ahat/src/style.css
new file mode 100644
index 0000000..ca074a5
--- /dev/null
+++ b/tools/ahat/src/style.css
@@ -0,0 +1,33 @@
+/*
+ * 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.
+ */
+
+div.menu {
+  background-color: #eeffff;
+}
+
+/*
+ * Most of the columns show numbers of bytes. Numbers should be right aligned.
+ */
+table td {
+  background-color: #eeeeee;
+  padding-left: 4px;
+  padding-right: 4px;
+}
+
+table th {
+  padding-left: 8px;
+  padding-right: 8px;
+}
diff --git a/tools/ahat/test/QueryTest.java b/tools/ahat/test/QueryTest.java
new file mode 100644
index 0000000..40e3322
--- /dev/null
+++ b/tools/ahat/test/QueryTest.java
@@ -0,0 +1,69 @@
+/*
+ * 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.net.URI;
+import java.net.URISyntaxException;
+import static org.junit.Assert.assertEquals;
+import org.junit.Test;
+
+public class QueryTest {
+  @Test
+  public void simple() throws URISyntaxException {
+    String uri = "http://localhost:7100/object?foo=bar&answer=42";
+    Query query = new Query(new URI(uri));
+    assertEquals("bar", query.get("foo", "not found"));
+    assertEquals("42", query.get("answer", "not found"));
+    assertEquals(42, query.getLong("answer", 0));
+    assertEquals(42, query.getInt("answer", 0));
+    assertEquals("not found", query.get("bar", "not found"));
+    assertEquals("really not found", query.get("bar", "really not found"));
+    assertEquals(0, query.getLong("bar", 0));
+    assertEquals(0, query.getInt("bar", 0));
+    assertEquals(42, query.getLong("bar", 42));
+    assertEquals(42, query.getInt("bar", 42));
+    assertEquals("/object?answer=42&foo=sludge", query.with("foo", "sludge").toString());
+    assertEquals("/object?answer=43&foo=bar", query.with("answer", "43").toString());
+    assertEquals("/object?answer=43&foo=bar", query.with("answer", 43).toString());
+    assertEquals("/object?answer=42&bar=finally&foo=bar", query.with("bar", "finally").toString());
+  }
+
+  @Test
+  public void multiValue() throws URISyntaxException {
+    String uri = "http://localhost:7100/object?foo=bar&answer=42&foo=sludge";
+    Query query = new Query(new URI(uri));
+    assertEquals("sludge", query.get("foo", "not found"));
+    assertEquals(42, query.getLong("answer", 0));
+    assertEquals(42, query.getInt("answer", 0));
+    assertEquals("not found", query.get("bar", "not found"));
+    assertEquals("/object?answer=42&foo=tar", query.with("foo", "tar").toString());
+    assertEquals("/object?answer=43&foo=sludge", query.with("answer", "43").toString());
+    assertEquals("/object?answer=42&bar=finally&foo=sludge",
+        query.with("bar", "finally").toString());
+  }
+
+  @Test
+  public void empty() throws URISyntaxException {
+    String uri = "http://localhost:7100/object";
+    Query query = new Query(new URI(uri));
+    assertEquals("not found", query.get("foo", "not found"));
+    assertEquals(2, query.getLong("foo", 2));
+    assertEquals(2, query.getInt("foo", 2));
+    assertEquals("/object?foo=sludge", query.with("foo", "sludge").toString());
+    assertEquals("/object?answer=43", query.with("answer", "43").toString());
+  }
+}
diff --git a/tools/ahat/test/SortTest.java b/tools/ahat/test/SortTest.java
new file mode 100644
index 0000000..02ff7db
--- /dev/null
+++ b/tools/ahat/test/SortTest.java
@@ -0,0 +1,81 @@
+/*
+ * 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/Tests.java b/tools/ahat/test/Tests.java
new file mode 100644
index 0000000..fb53d90
--- /dev/null
+++ b/tools/ahat/test/Tests.java
@@ -0,0 +1,32 @@
+/*
+ * 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 org.junit.runner.JUnitCore;
+
+public class Tests {
+  public static void main(String[] args) {
+    if (args.length == 0) {
+      args = new String[]{
+        "com.android.ahat.QueryTest",
+        "com.android.ahat.SortTest"
+      };
+    }
+    JUnitCore.main(args);
+  }
+}
+
diff --git a/tools/ahat/test/manifest.txt b/tools/ahat/test/manifest.txt
new file mode 100644
index 0000000..af17fad
--- /dev/null
+++ b/tools/ahat/test/manifest.txt
@@ -0,0 +1 @@
+Main-Class: com.android.ahat.Tests
diff --git a/tools/libcore_failures.txt b/tools/libcore_failures.txt
index 7ada189..728991d 100644
--- a/tools/libcore_failures.txt
+++ b/tools/libcore_failures.txt
@@ -130,7 +130,28 @@
   description: "Crypto failures",
   result: EXEC_FAILED,
   names: ["libcore.javax.crypto.CipherTest#testCipher_ShortBlock_Failure",
-          "libcore.javax.crypto.CipherTest#testCipher_Success"]
+          "libcore.javax.crypto.CipherTest#testCipher_Success",
+          "libcore.javax.crypto.spec.AlgorithmParametersTestDESede#testAlgorithmParameters",
+          "org.apache.harmony.crypto.tests.javax.crypto.CipherTest#testDoFinalbyteArrayintintbyteArrayint",
+          "org.apache.harmony.crypto.tests.javax.crypto.CipherTest#testUpdatebyteArrayintintbyteArrayint",
+          "org.apache.harmony.crypto.tests.javax.crypto.CipherTest#test_doFinal$BI",
+          "org.apache.harmony.crypto.tests.javax.crypto.CipherTest#test_doFinal$BII$B",
+          "org.apache.harmony.crypto.tests.javax.crypto.CipherTest#test_doFinalLjava_nio_ByteBufferLjava_nio_ByteBuffer",
+          "org.apache.harmony.crypto.tests.javax.crypto.CipherTest#test_getAlgorithm",
+          "org.apache.harmony.crypto.tests.javax.crypto.CipherTest#test_getBlockSize",
+          "org.apache.harmony.crypto.tests.javax.crypto.CipherTest#test_getInstanceLjava_lang_String",
+          "org.apache.harmony.crypto.tests.javax.crypto.CipherTest#test_getOutputSizeI",
+          "org.apache.harmony.crypto.tests.javax.crypto.CipherTest#test_initWithAlgorithmParameterSpec",
+          "org.apache.harmony.crypto.tests.javax.crypto.CipherTest#test_initWithKey",
+          "org.apache.harmony.crypto.tests.javax.crypto.CipherTest#test_initWithKeyAlgorithmParameterSpecSecureRandom",
+          "org.apache.harmony.crypto.tests.javax.crypto.CipherTest#test_initWithSecureRandom",
+          "org.apache.harmony.crypto.tests.javax.crypto.CipherTest#test_unwrap$BLjava_lang_StringI",
+          "org.apache.harmony.crypto.tests.javax.crypto.CipherTest#test_updateLjava_nio_ByteBufferLjava_nio_ByteBuffer",
+          "org.apache.harmony.crypto.tests.javax.crypto.func.CipherAesWrapTest#test_AesWrap",
+          "org.apache.harmony.crypto.tests.javax.crypto.func.CipherDESedeTest#test_DESedeISO",
+          "org.apache.harmony.crypto.tests.javax.crypto.func.CipherDESedeTest#test_DESedeNoISO",
+          "org.apache.harmony.crypto.tests.javax.crypto.func.CipherDESedeWrapTest#test_DESedeWrap",
+          "org.apache.harmony.crypto.tests.javax.crypto.func.CipherPBETest#test_PBEWithMD5AndDES"]
 },
 {
   description: "Flake when running with libartd.so or interpreter",