Merge "Revert "OatFileAssistant: look at vdex file for IsDexOptNeeded""
diff --git a/Android.bp b/Android.bp
index b9f1db5..d0e22fb 100644
--- a/Android.bp
+++ b/Android.bp
@@ -27,6 +27,7 @@
     "dexdump",
     "dexlayout",
     "dexlist",
+    "dexoptanalyzer",
     "disassembler",
     "imgdiag",
     "oatdump",
diff --git a/build/Android.common_build.mk b/build/Android.common_build.mk
index 4c82506..f5a95fa 100644
--- a/build/Android.common_build.mk
+++ b/build/Android.common_build.mk
@@ -46,6 +46,9 @@
 $(info Disabling ART_BUILD_HOST_DEBUG)
 endif
 
+# Enable the read barrier by default.
+ART_USE_READ_BARRIER ?= true
+
 ART_CPP_EXTENSION := .cc
 
 ifndef LIBART_IMG_HOST_BASE_ADDRESS
diff --git a/build/Android.common_path.mk b/build/Android.common_path.mk
index e568ce2..6de5aef 100644
--- a/build/Android.common_path.mk
+++ b/build/Android.common_path.mk
@@ -109,6 +109,7 @@
 
 ART_CORE_DEBUGGABLE_EXECUTABLES := \
     dex2oat \
+    dexoptanalyzer \
     imgdiag \
     oatdump \
     patchoat \
diff --git a/build/Android.gtest.mk b/build/Android.gtest.mk
index 5bdfbc7..bc08384 100644
--- a/build/Android.gtest.mk
+++ b/build/Android.gtest.mk
@@ -28,6 +28,7 @@
   DexToDexDecompiler \
   ErroneousA \
   ErroneousB \
+  ErroneousInit \
   ExceptionHandle \
   GetMethodSignature \
   ImageLayoutA \
@@ -87,7 +88,7 @@
 ART_GTEST_dex2oat_environment_tests_DEX_DEPS := Main MainStripped MultiDex MultiDexModifiedSecondary Nested
 
 ART_GTEST_atomic_method_ref_map_test_DEX_DEPS := Interfaces
-ART_GTEST_class_linker_test_DEX_DEPS := ErroneousA ErroneousB Interfaces MethodTypes MultiDex MyClass Nested Statics StaticsFromCode
+ART_GTEST_class_linker_test_DEX_DEPS := AllFields ErroneousA ErroneousB ErroneousInit Interfaces MethodTypes MultiDex MyClass Nested Statics StaticsFromCode
 ART_GTEST_class_table_test_DEX_DEPS := XandY
 ART_GTEST_compiler_driver_test_DEX_DEPS := AbstractMethod StaticLeafMethods ProfileTestMultiDex
 ART_GTEST_dex_cache_test_DEX_DEPS := Main Packages MethodTypes
@@ -100,6 +101,7 @@
 ART_GTEST_jni_compiler_test_DEX_DEPS := MyClassNatives
 ART_GTEST_jni_internal_test_DEX_DEPS := AllFields StaticLeafMethods
 ART_GTEST_oat_file_assistant_test_DEX_DEPS := $(ART_GTEST_dex2oat_environment_tests_DEX_DEPS)
+ART_GTEST_dexoptanalyzer_test_DEX_DEPS := $(ART_GTEST_dex2oat_environment_tests_DEX_DEPS)
 ART_GTEST_oat_file_test_DEX_DEPS := Main MultiDex
 ART_GTEST_oat_test_DEX_DEPS := Main
 ART_GTEST_object_test_DEX_DEPS := ProtoCompare ProtoCompare2 StaticsFromCode XandY
@@ -107,6 +109,7 @@
 ART_GTEST_reflection_test_DEX_DEPS := Main NonStaticLeafMethods StaticLeafMethods
 ART_GTEST_profile_assistant_test_DEX_DEPS := ProfileTestMultiDex
 ART_GTEST_profile_compilation_info_test_DEX_DEPS := ProfileTestMultiDex
+ART_GTEST_runtime_callbacks_test_DEX_DEPS := XandY
 ART_GTEST_stub_test_DEX_DEPS := AllFields
 ART_GTEST_transaction_test_DEX_DEPS := Transaction
 ART_GTEST_type_lookup_table_test_DEX_DEPS := Lookup
@@ -120,14 +123,14 @@
 ART_GTEST_dex2oat_environment_tests_HOST_DEPS := \
   $(HOST_CORE_IMAGE_optimizing_pic_64) \
   $(HOST_CORE_IMAGE_optimizing_pic_32) \
-  $(HOST_CORE_IMAGE_optimizing_no-pic_64) \
-  $(HOST_CORE_IMAGE_optimizing_no-pic_32) \
+  $(HOST_CORE_IMAGE_interpreter_pic_64) \
+  $(HOST_CORE_IMAGE_interpreter_pic_32) \
   $(HOST_OUT_EXECUTABLES)/patchoatd
 ART_GTEST_dex2oat_environment_tests_TARGET_DEPS := \
   $(TARGET_CORE_IMAGE_optimizing_pic_64) \
   $(TARGET_CORE_IMAGE_optimizing_pic_32) \
-  $(TARGET_CORE_IMAGE_optimizing_no-pic_64) \
-  $(TARGET_CORE_IMAGE_optimizing_no-pic_32) \
+  $(TARGET_CORE_IMAGE_interpreter_pic_64) \
+  $(TARGET_CORE_IMAGE_interpreter_pic_32) \
   $(TARGET_OUT_EXECUTABLES)/patchoatd
 
 ART_GTEST_oat_file_assistant_test_HOST_DEPS := \
@@ -135,6 +138,12 @@
 ART_GTEST_oat_file_assistant_test_TARGET_DEPS := \
   $(ART_GTEST_dex2oat_environment_tests_TARGET_DEPS)
 
+ART_GTEST_dexoptanalyzer_test_HOST_DEPS := \
+  $(ART_GTEST_dex2oat_environment_tests_HOST_DEPS) \
+  $(HOST_OUT_EXECUTABLES)/dexoptanalyzerd
+ART_GTEST_dexoptanalyzer_test_TARGET_DEPS := \
+  $(ART_GTEST_dex2oat_environment_tests_TARGET_DEPS) \
+  dexoptanalyzerd
 
 ART_GTEST_dex2oat_test_HOST_DEPS := \
   $(ART_GTEST_dex2oat_environment_tests_HOST_DEPS)
@@ -218,6 +227,7 @@
     art_dexdump_tests \
     art_dexlayout_tests \
     art_dexlist_tests \
+    art_dexoptanalyzer_tests \
     art_imgdiag_tests \
     art_oatdump_tests \
     art_profman_tests \
@@ -613,6 +623,9 @@
 ART_GTEST_oat_file_assistant_test_DEX_DEPS :=
 ART_GTEST_oat_file_assistant_test_HOST_DEPS :=
 ART_GTEST_oat_file_assistant_test_TARGET_DEPS :=
+ART_GTEST_dexoptanalyzer_test_DEX_DEPS :=
+ART_GTEST_dexoptanalyzer_test_HOST_DEPS :=
+ART_GTEST_dexoptanalyzer_test_TARGET_DEPS :=
 ART_GTEST_dex2oat_test_DEX_DEPS :=
 ART_GTEST_dex2oat_test_HOST_DEPS :=
 ART_GTEST_dex2oat_test_TARGET_DEPS :=
diff --git a/build/art.go b/build/art.go
index e6e0544..baa6e59 100644
--- a/build/art.go
+++ b/build/art.go
@@ -58,7 +58,7 @@
 		asflags = append(asflags, "-DART_HEAP_POISONING=1")
 	}
 
-	if envTrue(ctx, "ART_USE_READ_BARRIER") || ctx.AConfig().ArtUseReadBarrier() {
+	if !envFalse(ctx, "ART_USE_READ_BARRIER") && ctx.AConfig().ArtUseReadBarrier() {
 		// Used to change the read barrier type. Valid values are BAKER, BROOKS, TABLELOOKUP.
 		// The default is BAKER.
 		barrierType := envDefault(ctx, "ART_READ_BARRIER_TYPE", "BAKER")
diff --git a/compiler/common_compiler_test.h b/compiler/common_compiler_test.h
index f4838c1..0d45a50 100644
--- a/compiler/common_compiler_test.h
+++ b/compiler/common_compiler_test.h
@@ -23,7 +23,7 @@
 
 #include "common_runtime_test.h"
 #include "compiler.h"
-#include "jit/offline_profiling_info.h"
+#include "jit/profile_compilation_info.h"
 #include "oat_file.h"
 
 namespace art {
diff --git a/compiler/debug/elf_debug_line_writer.h b/compiler/debug/elf_debug_line_writer.h
index 3db7306..18a9165 100644
--- a/compiler/debug/elf_debug_line_writer.h
+++ b/compiler/debug/elf_debug_line_writer.h
@@ -53,7 +53,8 @@
   // Write line table for given set of methods.
   // Returns the number of bytes written.
   size_t WriteCompilationUnit(ElfCompilationUnit& compilation_unit) {
-    const bool is64bit = Is64BitInstructionSet(builder_->GetIsa());
+    const InstructionSet isa = builder_->GetIsa();
+    const bool is64bit = Is64BitInstructionSet(isa);
     const Elf_Addr base_address = compilation_unit.is_code_address_text_relative
         ? builder_->GetText()->GetAddress()
         : 0;
@@ -66,7 +67,7 @@
     std::unordered_map<std::string, size_t> directories_map;
     int code_factor_bits_ = 0;
     int dwarf_isa = -1;
-    switch (builder_->GetIsa()) {
+    switch (isa) {
       case kArm:  // arm actually means thumb2.
       case kThumb2:
         code_factor_bits_ = 1;  // 16-bit instuctions
@@ -103,7 +104,7 @@
         for (uint32_t s = 0; s < code_info.GetNumberOfStackMaps(encoding); s++) {
           StackMap stack_map = code_info.GetStackMapAt(s, encoding);
           DCHECK(stack_map.IsValid());
-          const uint32_t pc = stack_map.GetNativePcOffset(encoding.stack_map_encoding);
+          const uint32_t pc = stack_map.GetNativePcOffset(encoding.stack_map_encoding, isa);
           const int32_t dex = stack_map.GetDexPc(encoding.stack_map_encoding);
           pc2dex_map.push_back({pc, dex});
           if (stack_map.HasDexRegisterMap(encoding.stack_map_encoding)) {
diff --git a/compiler/debug/elf_debug_loc_writer.h b/compiler/debug/elf_debug_loc_writer.h
index 9645643..bce5387 100644
--- a/compiler/debug/elf_debug_loc_writer.h
+++ b/compiler/debug/elf_debug_loc_writer.h
@@ -92,7 +92,8 @@
     bool is64bitValue,
     uint64_t compilation_unit_code_address,
     uint32_t dex_pc_low,
-    uint32_t dex_pc_high) {
+    uint32_t dex_pc_high,
+    InstructionSet isa) {
   std::vector<VariableLocation> variable_locations;
 
   // Get stack maps sorted by pc (they might not be sorted internally).
@@ -111,7 +112,7 @@
       // The main reason for this is to save space by avoiding undefined gaps.
       continue;
     }
-    const uint32_t pc_offset = stack_map.GetNativePcOffset(encoding.stack_map_encoding);
+    const uint32_t pc_offset = stack_map.GetNativePcOffset(encoding.stack_map_encoding, isa);
     DCHECK_LE(pc_offset, method_info->code_size);
     DCHECK_LE(compilation_unit_code_address, method_info->code_address);
     const uint32_t low_pc = dchecked_integral_cast<uint32_t>(
@@ -196,7 +197,8 @@
       is64bitValue,
       compilation_unit_code_address,
       dex_pc_low,
-      dex_pc_high);
+      dex_pc_high,
+      isa);
 
   // Write .debug_loc entries.
   dwarf::Writer<> debug_loc(debug_loc_buffer);
diff --git a/compiler/driver/compiler_driver.cc b/compiler/driver/compiler_driver.cc
index faf8b41..b00d083 100644
--- a/compiler/driver/compiler_driver.cc
+++ b/compiler/driver/compiler_driver.cc
@@ -284,7 +284,7 @@
       verification_results_(verification_results),
       compiler_(Compiler::Create(this, compiler_kind)),
       compiler_kind_(compiler_kind),
-      instruction_set_(instruction_set == kArm ? kThumb2: instruction_set),
+      instruction_set_(instruction_set == kArm ? kThumb2 : instruction_set),
       instruction_set_features_(instruction_set_features),
       requires_constructor_barrier_lock_("constructor barrier lock"),
       compiled_classes_lock_("compiled classes lock"),
@@ -1251,7 +1251,7 @@
     }
   }
 
-  // java.lang.Reference visitor for VisitReferences.
+  // java.lang.ref.Reference visitor for VisitReferences.
   void operator()(ObjPtr<mirror::Class> klass ATTRIBUTE_UNUSED,
                   ObjPtr<mirror::Reference> ref ATTRIBUTE_UNUSED) const {}
 
@@ -2199,7 +2199,7 @@
     if (klass.Get() != nullptr) {
       // Only do this if the class is resolved. If even resolution fails, quickening will go very,
       // very wrong.
-      if (klass->IsResolved()) {
+      if (klass->IsResolved() && !klass->IsErroneousResolved()) {
         if (klass->GetStatus() < mirror::Class::kStatusVerified) {
           ObjectLock<mirror::Class> lock(soa.Self(), klass);
           // Set class status to verified.
@@ -2626,7 +2626,8 @@
 void CompilerDriver::RecordClassStatus(ClassReference ref, mirror::Class::Status status) {
   switch (status) {
     case mirror::Class::kStatusNotReady:
-    case mirror::Class::kStatusError:
+    case mirror::Class::kStatusErrorResolved:
+    case mirror::Class::kStatusErrorUnresolved:
     case mirror::Class::kStatusRetryVerificationAtRuntime:
     case mirror::Class::kStatusVerified:
     case mirror::Class::kStatusInitialized:
diff --git a/compiler/driver/compiler_driver.h b/compiler/driver/compiler_driver.h
index 6bfdd4d..503fe3a 100644
--- a/compiler/driver/compiler_driver.h
+++ b/compiler/driver/compiler_driver.h
@@ -33,7 +33,7 @@
 #include "dex_file.h"
 #include "dex_file_types.h"
 #include "driver/compiled_method_storage.h"
-#include "jit/offline_profiling_info.h"
+#include "jit/profile_compilation_info.h"
 #include "invoke_type.h"
 #include "method_reference.h"
 #include "mirror/class.h"  // For mirror::Class::Status.
diff --git a/compiler/driver/compiler_driver_test.cc b/compiler/driver/compiler_driver_test.cc
index 12684c0..1e4ca16 100644
--- a/compiler/driver/compiler_driver_test.cc
+++ b/compiler/driver/compiler_driver_test.cc
@@ -32,7 +32,7 @@
 #include "mirror/object_array-inl.h"
 #include "mirror/object-inl.h"
 #include "handle_scope-inl.h"
-#include "jit/offline_profiling_info.h"
+#include "jit/profile_compilation_info.h"
 #include "scoped_thread_state_change-inl.h"
 
 namespace art {
diff --git a/compiler/exception_test.cc b/compiler/exception_test.cc
index f9e5cb9..eac46e5 100644
--- a/compiler/exception_test.cc
+++ b/compiler/exception_test.cc
@@ -61,7 +61,7 @@
 
     ArenaPool pool;
     ArenaAllocator allocator(&pool);
-    StackMapStream stack_maps(&allocator);
+    StackMapStream stack_maps(&allocator, kRuntimeISA);
     stack_maps.BeginStackMapEntry(/* dex_pc */ 3u,
                                   /* native_pc_offset */ 3u,
                                   /* register_mask */ 0u,
diff --git a/compiler/image_writer.cc b/compiler/image_writer.cc
index 4109345..15e4cd8 100644
--- a/compiler/image_writer.cc
+++ b/compiler/image_writer.cc
@@ -756,7 +756,7 @@
   bool my_early_exit = false;  // Only for ourselves, ignore caller.
   // Remove classes that failed to verify since we don't want to have java.lang.VerifyError in the
   // app image.
-  if (klass->GetStatus() == mirror::Class::kStatusError) {
+  if (klass->IsErroneous()) {
     result = true;
   } else {
     ObjPtr<mirror::ClassExt> ext(klass->GetExtData());
@@ -777,8 +777,8 @@
                                                   visited);
   }
   // Check static fields and their classes.
-  size_t num_static_fields = klass->NumReferenceStaticFields();
-  if (num_static_fields != 0 && klass->IsResolved()) {
+  if (klass->IsResolved() && klass->NumReferenceStaticFields() != 0) {
+    size_t num_static_fields = klass->NumReferenceStaticFields();
     // Presumably GC can happen when we are cross compiling, it should not cause performance
     // problems to do pointer size logic.
     MemberOffset field_offset = klass->GetFirstReferenceStaticFieldOffset(
@@ -1154,7 +1154,7 @@
       // Visit and assign offsets for fields and field arrays.
       mirror::Class* as_klass = obj->AsClass();
       mirror::DexCache* dex_cache = as_klass->GetDexCache();
-      DCHECK_NE(as_klass->GetStatus(), mirror::Class::kStatusError);
+      DCHECK(!as_klass->IsErroneous()) << as_klass->GetStatus();
       if (compile_app_image_) {
         // Extra sanity, no boot loader classes should be left!
         CHECK(!IsBootClassLoaderClass(as_klass)) << as_klass->PrettyClass();
@@ -1166,9 +1166,9 @@
       // belongs.
       oat_index = GetOatIndexForDexCache(dex_cache);
       ImageInfo& image_info = GetImageInfo(oat_index);
-      {
-        // Note: This table is only accessed from the image writer, avoid locking to prevent lock
-        // order violations from root visiting.
+      if (!compile_app_image_) {
+        // Note: Avoid locking to prevent lock order violations from root visiting;
+        // image_info.class_table_ is only accessed from the image writer.
         image_info.class_table_->InsertWithoutLocks(as_klass);
       }
       for (LengthPrefixedArray<ArtField>* cur_fields : fields) {
@@ -1265,7 +1265,14 @@
       // class loader.
       mirror::ClassLoader* class_loader = obj->AsClassLoader();
       if (class_loader->GetClassTable() != nullptr) {
+        DCHECK(compile_app_image_);
+        DCHECK(class_loaders_.empty());
         class_loaders_.insert(class_loader);
+        ImageInfo& image_info = GetImageInfo(oat_index);
+        // Note: Avoid locking to prevent lock order violations from root visiting;
+        // image_info.class_table_ table is only accessed from the image writer
+        // and class_loader->GetClassTable() is iterated but not modified.
+        image_info.class_table_->CopyWithoutLocks(*class_loader->GetClassTable());
       }
     }
     AssignImageBinSlot(obj, oat_index);
diff --git a/compiler/jni/quick/x86_64/calling_convention_x86_64.cc b/compiler/jni/quick/x86_64/calling_convention_x86_64.cc
index 8ca0ffe..ba654f4 100644
--- a/compiler/jni/quick/x86_64/calling_convention_x86_64.cc
+++ b/compiler/jni/quick/x86_64/calling_convention_x86_64.cc
@@ -160,7 +160,7 @@
     while (HasNext()) {
       ManagedRegister in_reg = CurrentParamRegister();
       if (!in_reg.IsNoRegister()) {
-        int32_t size = IsParamALongOrDouble(itr_args_)? 8 : 4;
+        int32_t size = IsParamALongOrDouble(itr_args_) ? 8 : 4;
         int32_t spill_offset = CurrentParamStackOffset().Uint32Value();
         ManagedRegisterSpill spill(in_reg, size, spill_offset);
         entry_spills_.push_back(spill);
diff --git a/compiler/oat_writer.cc b/compiler/oat_writer.cc
index de5af97..bd2c5e3 100644
--- a/compiler/oat_writer.cc
+++ b/compiler/oat_writer.cc
@@ -716,7 +716,10 @@
     if (compiled_class != nullptr) {
       status = compiled_class->GetStatus();
     } else if (writer_->compiler_driver_->GetVerificationResults()->IsClassRejected(class_ref)) {
-      status = mirror::Class::kStatusError;
+      // The oat class status is used only for verification of resolved classes,
+      // so use kStatusErrorResolved whether the class was resolved or unresolved
+      // during compile-time verification.
+      status = mirror::Class::kStatusErrorResolved;
     } else {
       status = mirror::Class::kStatusNotReady;
     }
diff --git a/compiler/optimizing/code_generator.cc b/compiler/optimizing/code_generator.cc
index 70c2738..99427f0 100644
--- a/compiler/optimizing/code_generator.cc
+++ b/compiler/optimizing/code_generator.cc
@@ -839,8 +839,8 @@
     // last emitted is different than the native pc of the stack map just emitted.
     size_t number_of_stack_maps = stack_map_stream_.GetNumberOfStackMaps();
     if (number_of_stack_maps > 1) {
-      DCHECK_NE(stack_map_stream_.GetStackMap(number_of_stack_maps - 1).native_pc_offset,
-                stack_map_stream_.GetStackMap(number_of_stack_maps - 2).native_pc_offset);
+      DCHECK_NE(stack_map_stream_.GetStackMap(number_of_stack_maps - 1).native_pc_code_offset,
+                stack_map_stream_.GetStackMap(number_of_stack_maps - 2).native_pc_code_offset);
     }
   }
 }
@@ -848,7 +848,8 @@
 bool CodeGenerator::HasStackMapAtCurrentPc() {
   uint32_t pc = GetAssembler()->CodeSize();
   size_t count = stack_map_stream_.GetNumberOfStackMaps();
-  return count > 0 && stack_map_stream_.GetStackMap(count - 1).native_pc_offset == pc;
+  CodeOffset native_pc_offset = stack_map_stream_.GetStackMap(count - 1).native_pc_code_offset;
+  return (count > 0) && (native_pc_offset.Uint32Value(GetInstructionSet()) == pc);
 }
 
 void CodeGenerator::MaybeRecordNativeDebugInfo(HInstruction* instruction,
diff --git a/compiler/optimizing/code_generator.h b/compiler/optimizing/code_generator.h
index 38d532e..2d129af 100644
--- a/compiler/optimizing/code_generator.h
+++ b/compiler/optimizing/code_generator.h
@@ -608,7 +608,7 @@
         number_of_register_pairs_(number_of_register_pairs),
         core_callee_save_mask_(core_callee_save_mask),
         fpu_callee_save_mask_(fpu_callee_save_mask),
-        stack_map_stream_(graph->GetArena()),
+        stack_map_stream_(graph->GetArena(), graph->GetInstructionSet()),
         block_order_(nullptr),
         jit_string_roots_(StringReferenceValueComparator(),
                           graph->GetArena()->Adapter(kArenaAllocCodeGenerator)),
diff --git a/compiler/optimizing/code_generator_arm.cc b/compiler/optimizing/code_generator_arm.cc
index 9c9c604..f5b6ebe 100644
--- a/compiler/optimizing/code_generator_arm.cc
+++ b/compiler/optimizing/code_generator_arm.cc
@@ -1239,7 +1239,8 @@
 
   // Adjust native pc offsets in stack maps.
   for (size_t i = 0, num = stack_map_stream_.GetNumberOfStackMaps(); i != num; ++i) {
-    uint32_t old_position = stack_map_stream_.GetStackMap(i).native_pc_offset;
+    uint32_t old_position =
+        stack_map_stream_.GetStackMap(i).native_pc_code_offset.Uint32Value(kThumb2);
     uint32_t new_position = __ GetAdjustedPosition(old_position);
     stack_map_stream_.SetStackMapNativePcOffset(i, new_position);
   }
@@ -3331,7 +3332,7 @@
         InvokeRuntimeCallingConvention calling_convention;
         locations->SetInAt(0, Location::RegisterLocation(calling_convention.GetRegisterAt(0)));
         locations->SetInAt(1, Location::RegisterLocation(calling_convention.GetRegisterAt(1)));
-        // Note: divrem will compute both the quotient and the remainder as the pair R0 and R1, but
+        // Note: divmod will compute both the quotient and the remainder as the pair R0 and R1, but
         //       we only need the former.
         locations->SetOut(Location::RegisterLocation(R0));
       }
@@ -3458,7 +3459,7 @@
         InvokeRuntimeCallingConvention calling_convention;
         locations->SetInAt(0, Location::RegisterLocation(calling_convention.GetRegisterAt(0)));
         locations->SetInAt(1, Location::RegisterLocation(calling_convention.GetRegisterAt(1)));
-        // Note: divrem will compute both the quotient and the remainder as the pair R0 and R1, but
+        // Note: divmod will compute both the quotient and the remainder as the pair R0 and R1, but
         //       we only need the latter.
         locations->SetOut(Location::RegisterLocation(R1));
       }
@@ -7150,18 +7151,7 @@
 HInvokeStaticOrDirect::DispatchInfo CodeGeneratorARM::GetSupportedInvokeStaticOrDirectDispatch(
       const HInvokeStaticOrDirect::DispatchInfo& desired_dispatch_info,
       HInvokeStaticOrDirect* invoke ATTRIBUTE_UNUSED) {
-  HInvokeStaticOrDirect::DispatchInfo dispatch_info = desired_dispatch_info;
-  // We disable pc-relative load when there is an irreducible loop, as the optimization
-  // is incompatible with it.
-  // TODO: Create as many ArmDexCacheArraysBase instructions as needed for methods
-  // with irreducible loops.
-  if (GetGraph()->HasIrreducibleLoops() &&
-      (dispatch_info.method_load_kind ==
-          HInvokeStaticOrDirect::MethodLoadKind::kDexCachePcRelative)) {
-    dispatch_info.method_load_kind = HInvokeStaticOrDirect::MethodLoadKind::kDexCacheViaMethod;
-  }
-
-  return dispatch_info;
+  return desired_dispatch_info;
 }
 
 Register CodeGeneratorARM::GetInvokeStaticOrDirectExtraParameter(HInvokeStaticOrDirect* invoke,
@@ -7181,8 +7171,7 @@
   // save one load. However, since this is just an intrinsic slow path we prefer this
   // simple and more robust approach rather that trying to determine if that's the case.
   SlowPathCode* slow_path = GetCurrentSlowPath();
-  DCHECK(slow_path != nullptr);  // For intrinsified invokes the call is emitted on the slow path.
-  if (slow_path->IsCoreRegisterSaved(location.AsRegister<Register>())) {
+  if (slow_path != nullptr && slow_path->IsCoreRegisterSaved(location.AsRegister<Register>())) {
     int stack_offset = slow_path->GetStackOffsetOfCoreRegister(location.AsRegister<Register>());
     __ LoadFromOffset(kLoadWord, temp, SP, stack_offset);
     return temp;
@@ -7190,7 +7179,8 @@
   return location.AsRegister<Register>();
 }
 
-void CodeGeneratorARM::GenerateStaticOrDirectCall(HInvokeStaticOrDirect* invoke, Location temp) {
+Location CodeGeneratorARM::GenerateCalleeMethodStaticOrDirectCall(HInvokeStaticOrDirect* invoke,
+                                                                  Location temp) {
   Location callee_method = temp;  // For all kinds except kRecursive, callee will be in temp.
   switch (invoke->GetMethodLoadKind()) {
     case HInvokeStaticOrDirect::MethodLoadKind::kStringInit: {
@@ -7239,6 +7229,11 @@
       break;
     }
   }
+  return callee_method;
+}
+
+void CodeGeneratorARM::GenerateStaticOrDirectCall(HInvokeStaticOrDirect* invoke, Location temp) {
+  Location callee_method = GenerateCalleeMethodStaticOrDirectCall(invoke, temp);
 
   switch (invoke->GetCodePtrLocation()) {
     case HInvokeStaticOrDirect::CodePtrLocation::kCallSelf:
diff --git a/compiler/optimizing/code_generator_arm.h b/compiler/optimizing/code_generator_arm.h
index 52d1857..df2dbc7 100644
--- a/compiler/optimizing/code_generator_arm.h
+++ b/compiler/optimizing/code_generator_arm.h
@@ -456,6 +456,7 @@
       const HInvokeStaticOrDirect::DispatchInfo& desired_dispatch_info,
       HInvokeStaticOrDirect* invoke) OVERRIDE;
 
+  Location GenerateCalleeMethodStaticOrDirectCall(HInvokeStaticOrDirect* invoke, Location temp);
   void GenerateStaticOrDirectCall(HInvokeStaticOrDirect* invoke, Location temp) OVERRIDE;
   void GenerateVirtualCall(HInvokeVirtual* invoke, Location temp) OVERRIDE;
 
diff --git a/compiler/optimizing/code_generator_arm64.cc b/compiler/optimizing/code_generator_arm64.cc
index 68d0b86..9762ee8 100644
--- a/compiler/optimizing/code_generator_arm64.cc
+++ b/compiler/optimizing/code_generator_arm64.cc
@@ -3993,7 +3993,8 @@
   return desired_dispatch_info;
 }
 
-void CodeGeneratorARM64::GenerateStaticOrDirectCall(HInvokeStaticOrDirect* invoke, Location temp) {
+Location CodeGeneratorARM64::GenerateCalleeMethodStaticOrDirectCall(HInvokeStaticOrDirect* invoke,
+                                                                    Location temp) {
   // Make sure that ArtMethod* is passed in kArtMethodRegister as per the calling convention.
   Location callee_method = temp;  // For all kinds except kRecursive, callee will be in temp.
   switch (invoke->GetMethodLoadKind()) {
@@ -4047,6 +4048,12 @@
       break;
     }
   }
+  return callee_method;
+}
+
+void CodeGeneratorARM64::GenerateStaticOrDirectCall(HInvokeStaticOrDirect* invoke, Location temp) {
+  // All registers are assumed to be correctly set up.
+  Location callee_method = GenerateCalleeMethodStaticOrDirectCall(invoke, temp);
 
   switch (invoke->GetCodePtrLocation()) {
     case HInvokeStaticOrDirect::CodePtrLocation::kCallSelf:
@@ -4655,7 +4662,7 @@
 }
 
 void InstructionCodeGeneratorARM64::VisitMonitorOperation(HMonitorOperation* instruction) {
-  codegen_->InvokeRuntime(instruction->IsEnter() ? kQuickLockObject: kQuickUnlockObject,
+  codegen_->InvokeRuntime(instruction->IsEnter() ? kQuickLockObject : kQuickUnlockObject,
                           instruction,
                           instruction->GetDexPc());
   if (instruction->IsEnter()) {
diff --git a/compiler/optimizing/code_generator_arm64.h b/compiler/optimizing/code_generator_arm64.h
index a9dca92..7d3c655 100644
--- a/compiler/optimizing/code_generator_arm64.h
+++ b/compiler/optimizing/code_generator_arm64.h
@@ -527,6 +527,7 @@
       const HInvokeStaticOrDirect::DispatchInfo& desired_dispatch_info,
       HInvokeStaticOrDirect* invoke) OVERRIDE;
 
+  Location GenerateCalleeMethodStaticOrDirectCall(HInvokeStaticOrDirect* invoke, Location temp);
   void GenerateStaticOrDirectCall(HInvokeStaticOrDirect* invoke, Location temp) OVERRIDE;
   void GenerateVirtualCall(HInvokeVirtual* invoke, Location temp) OVERRIDE;
 
diff --git a/compiler/optimizing/code_generator_arm_vixl.cc b/compiler/optimizing/code_generator_arm_vixl.cc
index 592ee5a..ffaf18f 100644
--- a/compiler/optimizing/code_generator_arm_vixl.cc
+++ b/compiler/optimizing/code_generator_arm_vixl.cc
@@ -3336,7 +3336,7 @@
         InvokeRuntimeCallingConventionARMVIXL calling_convention;
         locations->SetInAt(0, LocationFrom(calling_convention.GetRegisterAt(0)));
         locations->SetInAt(1, LocationFrom(calling_convention.GetRegisterAt(1)));
-        // Note: divrem will compute both the quotient and the remainder as the pair R0 and R1, but
+        // Note: divmod will compute both the quotient and the remainder as the pair R0 and R1, but
         //       we only need the former.
         locations->SetOut(LocationFrom(r0));
       }
@@ -3450,7 +3450,7 @@
         InvokeRuntimeCallingConventionARMVIXL calling_convention;
         locations->SetInAt(0, LocationFrom(calling_convention.GetRegisterAt(0)));
         locations->SetInAt(1, LocationFrom(calling_convention.GetRegisterAt(1)));
-        // Note: divrem will compute both the quotient and the remainder as the pair R0 and R1, but
+        // Note: divmod will compute both the quotient and the remainder as the pair R0 and R1, but
         //       we only need the latter.
         locations->SetOut(LocationFrom(r1));
       }
@@ -7233,18 +7233,7 @@
 HInvokeStaticOrDirect::DispatchInfo CodeGeneratorARMVIXL::GetSupportedInvokeStaticOrDirectDispatch(
     const HInvokeStaticOrDirect::DispatchInfo& desired_dispatch_info,
     HInvokeStaticOrDirect* invoke ATTRIBUTE_UNUSED) {
-  HInvokeStaticOrDirect::DispatchInfo dispatch_info = desired_dispatch_info;
-  // We disable pc-relative load when there is an irreducible loop, as the optimization
-  // is incompatible with it.
-  // TODO: Create as many ArmDexCacheArraysBase instructions as needed for methods
-  // with irreducible loops.
-  if (GetGraph()->HasIrreducibleLoops() &&
-      (dispatch_info.method_load_kind ==
-          HInvokeStaticOrDirect::MethodLoadKind::kDexCachePcRelative)) {
-    dispatch_info.method_load_kind = HInvokeStaticOrDirect::MethodLoadKind::kDexCacheViaMethod;
-  }
-
-  return dispatch_info;
+  return desired_dispatch_info;
 }
 
 vixl32::Register CodeGeneratorARMVIXL::GetInvokeStaticOrDirectExtraParameter(
@@ -7273,7 +7262,7 @@
   return RegisterFrom(location);
 }
 
-void CodeGeneratorARMVIXL::GenerateStaticOrDirectCall(
+Location CodeGeneratorARMVIXL::GenerateCalleeMethodStaticOrDirectCall(
     HInvokeStaticOrDirect* invoke, Location temp) {
   Location callee_method = temp;  // For all kinds except kRecursive, callee will be in temp.
   switch (invoke->GetMethodLoadKind()) {
@@ -7324,6 +7313,12 @@
       break;
     }
   }
+  return callee_method;
+}
+
+void CodeGeneratorARMVIXL::GenerateStaticOrDirectCall(HInvokeStaticOrDirect* invoke,
+                                                      Location temp) {
+  Location callee_method = GenerateCalleeMethodStaticOrDirectCall(invoke, temp);
 
   switch (invoke->GetCodePtrLocation()) {
     case HInvokeStaticOrDirect::CodePtrLocation::kCallSelf:
diff --git a/compiler/optimizing/code_generator_arm_vixl.h b/compiler/optimizing/code_generator_arm_vixl.h
index be65353..8ae3b7d 100644
--- a/compiler/optimizing/code_generator_arm_vixl.h
+++ b/compiler/optimizing/code_generator_arm_vixl.h
@@ -537,6 +537,7 @@
       const HInvokeStaticOrDirect::DispatchInfo& desired_dispatch_info,
       HInvokeStaticOrDirect* invoke) OVERRIDE;
 
+  Location GenerateCalleeMethodStaticOrDirectCall(HInvokeStaticOrDirect* invoke, Location temp);
   void GenerateStaticOrDirectCall(HInvokeStaticOrDirect* invoke, Location temp) OVERRIDE;
   void GenerateVirtualCall(HInvokeVirtual* invoke, Location temp) OVERRIDE;
 
diff --git a/compiler/optimizing/code_generator_mips.cc b/compiler/optimizing/code_generator_mips.cc
index a038382..76be74e 100644
--- a/compiler/optimizing/code_generator_mips.cc
+++ b/compiler/optimizing/code_generator_mips.cc
@@ -496,7 +496,8 @@
 
   // Adjust native pc offsets in stack maps.
   for (size_t i = 0, num = stack_map_stream_.GetNumberOfStackMaps(); i != num; ++i) {
-    uint32_t old_position = stack_map_stream_.GetStackMap(i).native_pc_offset;
+    uint32_t old_position =
+        stack_map_stream_.GetStackMap(i).native_pc_code_offset.Uint32Value(kMips);
     uint32_t new_position = __ GetAdjustedPosition(old_position);
     DCHECK_GE(new_position, old_position);
     stack_map_stream_.SetStackMapNativePcOffset(i, new_position);
diff --git a/compiler/optimizing/code_generator_mips64.cc b/compiler/optimizing/code_generator_mips64.cc
index 446dea6..192b4a5 100644
--- a/compiler/optimizing/code_generator_mips64.cc
+++ b/compiler/optimizing/code_generator_mips64.cc
@@ -450,7 +450,8 @@
 
   // Adjust native pc offsets in stack maps.
   for (size_t i = 0, num = stack_map_stream_.GetNumberOfStackMaps(); i != num; ++i) {
-    uint32_t old_position = stack_map_stream_.GetStackMap(i).native_pc_offset;
+    uint32_t old_position =
+        stack_map_stream_.GetStackMap(i).native_pc_code_offset.Uint32Value(kMips64);
     uint32_t new_position = __ GetAdjustedPosition(old_position);
     DCHECK_GE(new_position, old_position);
     stack_map_stream_.SetStackMapNativePcOffset(i, new_position);
diff --git a/compiler/optimizing/code_generator_x86.cc b/compiler/optimizing/code_generator_x86.cc
index 853c91f..1b74316 100644
--- a/compiler/optimizing/code_generator_x86.cc
+++ b/compiler/optimizing/code_generator_x86.cc
@@ -1023,7 +1023,8 @@
       jit_class_patches_(graph->GetArena()->Adapter(kArenaAllocCodeGenerator)),
       constant_area_start_(-1),
       fixups_to_jump_tables_(graph->GetArena()->Adapter(kArenaAllocCodeGenerator)),
-      method_address_offset_(-1) {
+      method_address_offset_(std::less<uint32_t>(),
+                             graph->GetArena()->Adapter(kArenaAllocCodeGenerator)) {
   // Use a fake return address register to mimic Quick.
   AddAllocatedRegister(Location::RegisterLocation(kFakeReturnRegister));
 }
@@ -1498,8 +1499,9 @@
       DCHECK(const_area->IsEmittedAtUseSite());
       __ ucomisd(lhs.AsFpuRegister<XmmRegister>(),
                  codegen_->LiteralDoubleAddress(
-                   const_area->GetConstant()->AsDoubleConstant()->GetValue(),
-                   const_area->GetLocations()->InAt(0).AsRegister<Register>()));
+                     const_area->GetConstant()->AsDoubleConstant()->GetValue(),
+                     const_area->GetBaseMethodAddress(),
+                     const_area->GetLocations()->InAt(0).AsRegister<Register>()));
     } else {
       DCHECK(rhs.IsDoubleStackSlot());
       __ ucomisd(lhs.AsFpuRegister<XmmRegister>(), Address(ESP, rhs.GetStackIndex()));
@@ -1511,8 +1513,9 @@
       DCHECK(const_area->IsEmittedAtUseSite());
       __ ucomiss(lhs.AsFpuRegister<XmmRegister>(),
                  codegen_->LiteralFloatAddress(
-                   const_area->GetConstant()->AsFloatConstant()->GetValue(),
-                   const_area->GetLocations()->InAt(0).AsRegister<Register>()));
+                     const_area->GetConstant()->AsFloatConstant()->GetValue(),
+                     const_area->GetBaseMethodAddress(),
+                     const_area->GetLocations()->InAt(0).AsRegister<Register>()));
     } else {
       DCHECK(rhs.IsStackSlot());
       __ ucomiss(lhs.AsFpuRegister<XmmRegister>(), Address(ESP, rhs.GetStackIndex()));
@@ -1778,7 +1781,7 @@
         cond = X86Condition(condition->GetCondition());
       }
     } else {
-      // Must be a boolean condition, which needs to be compared to 0.
+      // Must be a Boolean condition, which needs to be compared to 0.
       Register cond_reg = locations->InAt(2).AsRegister<Register>();
       __ testl(cond_reg, cond_reg);
     }
@@ -2360,10 +2363,14 @@
   Register constant_area = locations->InAt(1).AsRegister<Register>();
   XmmRegister mask = locations->GetTemp(0).AsFpuRegister<XmmRegister>();
   if (neg->GetType() == Primitive::kPrimFloat) {
-    __ movss(mask, codegen_->LiteralInt32Address(INT32_C(0x80000000), constant_area));
+    __ movss(mask, codegen_->LiteralInt32Address(INT32_C(0x80000000),
+                                                 neg->GetBaseMethodAddress(),
+                                                 constant_area));
     __ xorps(out.AsFpuRegister<XmmRegister>(), mask);
   } else {
-     __ movsd(mask, codegen_->LiteralInt64Address(INT64_C(0x8000000000000000), constant_area));
+     __ movsd(mask, codegen_->LiteralInt64Address(INT64_C(0x8000000000000000),
+                                                  neg->GetBaseMethodAddress(),
+                                                  constant_area));
      __ xorpd(out.AsFpuRegister<XmmRegister>(), mask);
   }
 }
@@ -3012,8 +3019,9 @@
         DCHECK(const_area->IsEmittedAtUseSite());
         __ addss(first.AsFpuRegister<XmmRegister>(),
                  codegen_->LiteralFloatAddress(
-                   const_area->GetConstant()->AsFloatConstant()->GetValue(),
-                   const_area->GetLocations()->InAt(0).AsRegister<Register>()));
+                     const_area->GetConstant()->AsFloatConstant()->GetValue(),
+                     const_area->GetBaseMethodAddress(),
+                     const_area->GetLocations()->InAt(0).AsRegister<Register>()));
       } else {
         DCHECK(second.IsStackSlot());
         __ addss(first.AsFpuRegister<XmmRegister>(), Address(ESP, second.GetStackIndex()));
@@ -3029,8 +3037,9 @@
         DCHECK(const_area->IsEmittedAtUseSite());
         __ addsd(first.AsFpuRegister<XmmRegister>(),
                  codegen_->LiteralDoubleAddress(
-                   const_area->GetConstant()->AsDoubleConstant()->GetValue(),
-                   const_area->GetLocations()->InAt(0).AsRegister<Register>()));
+                     const_area->GetConstant()->AsDoubleConstant()->GetValue(),
+                     const_area->GetBaseMethodAddress(),
+                     const_area->GetLocations()->InAt(0).AsRegister<Register>()));
       } else {
         DCHECK(second.IsDoubleStackSlot());
         __ addsd(first.AsFpuRegister<XmmRegister>(), Address(ESP, second.GetStackIndex()));
@@ -3116,8 +3125,9 @@
         DCHECK(const_area->IsEmittedAtUseSite());
         __ subss(first.AsFpuRegister<XmmRegister>(),
                  codegen_->LiteralFloatAddress(
-                   const_area->GetConstant()->AsFloatConstant()->GetValue(),
-                   const_area->GetLocations()->InAt(0).AsRegister<Register>()));
+                     const_area->GetConstant()->AsFloatConstant()->GetValue(),
+                     const_area->GetBaseMethodAddress(),
+                     const_area->GetLocations()->InAt(0).AsRegister<Register>()));
       } else {
         DCHECK(second.IsStackSlot());
         __ subss(first.AsFpuRegister<XmmRegister>(), Address(ESP, second.GetStackIndex()));
@@ -3134,6 +3144,7 @@
         __ subsd(first.AsFpuRegister<XmmRegister>(),
                  codegen_->LiteralDoubleAddress(
                      const_area->GetConstant()->AsDoubleConstant()->GetValue(),
+                     const_area->GetBaseMethodAddress(),
                      const_area->GetLocations()->InAt(0).AsRegister<Register>()));
       } else {
         DCHECK(second.IsDoubleStackSlot());
@@ -3304,6 +3315,7 @@
         __ mulss(first.AsFpuRegister<XmmRegister>(),
                  codegen_->LiteralFloatAddress(
                      const_area->GetConstant()->AsFloatConstant()->GetValue(),
+                     const_area->GetBaseMethodAddress(),
                      const_area->GetLocations()->InAt(0).AsRegister<Register>()));
       } else {
         DCHECK(second.IsStackSlot());
@@ -3322,6 +3334,7 @@
         __ mulsd(first.AsFpuRegister<XmmRegister>(),
                  codegen_->LiteralDoubleAddress(
                      const_area->GetConstant()->AsDoubleConstant()->GetValue(),
+                     const_area->GetBaseMethodAddress(),
                      const_area->GetLocations()->InAt(0).AsRegister<Register>()));
       } else {
         DCHECK(second.IsDoubleStackSlot());
@@ -3690,6 +3703,7 @@
         __ divss(first.AsFpuRegister<XmmRegister>(),
                  codegen_->LiteralFloatAddress(
                    const_area->GetConstant()->AsFloatConstant()->GetValue(),
+                   const_area->GetBaseMethodAddress(),
                    const_area->GetLocations()->InAt(0).AsRegister<Register>()));
       } else {
         DCHECK(second.IsStackSlot());
@@ -3706,8 +3720,9 @@
         DCHECK(const_area->IsEmittedAtUseSite());
         __ divsd(first.AsFpuRegister<XmmRegister>(),
                  codegen_->LiteralDoubleAddress(
-                   const_area->GetConstant()->AsDoubleConstant()->GetValue(),
-                   const_area->GetLocations()->InAt(0).AsRegister<Register>()));
+                     const_area->GetConstant()->AsDoubleConstant()->GetValue(),
+                     const_area->GetBaseMethodAddress(),
+                     const_area->GetLocations()->InAt(0).AsRegister<Register>()));
       } else {
         DCHECK(second.IsDoubleStackSlot());
         __ divsd(first.AsFpuRegister<XmmRegister>(), Address(ESP, second.GetStackIndex()));
@@ -4454,18 +4469,7 @@
 HInvokeStaticOrDirect::DispatchInfo CodeGeneratorX86::GetSupportedInvokeStaticOrDirectDispatch(
       const HInvokeStaticOrDirect::DispatchInfo& desired_dispatch_info,
       HInvokeStaticOrDirect* invoke ATTRIBUTE_UNUSED) {
-  HInvokeStaticOrDirect::DispatchInfo dispatch_info = desired_dispatch_info;
-
-  // We disable pc-relative load when there is an irreducible loop, as the optimization
-  // is incompatible with it.
-  // TODO: Create as many X86ComputeBaseMethodAddress instructions
-  // as needed for methods with irreducible loops.
-  if (GetGraph()->HasIrreducibleLoops() &&
-      (dispatch_info.method_load_kind ==
-          HInvokeStaticOrDirect::MethodLoadKind::kDexCachePcRelative)) {
-    dispatch_info.method_load_kind = HInvokeStaticOrDirect::MethodLoadKind::kDexCacheViaMethod;
-  }
-  return dispatch_info;
+  return desired_dispatch_info;
 }
 
 Register CodeGeneratorX86::GetInvokeStaticOrDirectExtraParameter(HInvokeStaticOrDirect* invoke,
@@ -4518,7 +4522,10 @@
       __ movl(temp.AsRegister<Register>(), Address(base_reg, kDummy32BitOffset));
       // Bind a new fixup label at the end of the "movl" insn.
       uint32_t offset = invoke->GetDexCacheArrayOffset();
-      __ Bind(NewPcRelativeDexCacheArrayPatch(invoke->GetDexFileForPcRelativeDexCache(), offset));
+      __ Bind(NewPcRelativeDexCacheArrayPatch(
+          invoke->InputAt(invoke->GetSpecialInputIndex())->AsX86ComputeBaseMethodAddress(),
+          invoke->GetDexFileForPcRelativeDexCache(),
+          offset));
       break;
     }
     case HInvokeStaticOrDirect::MethodLoadKind::kDexCacheViaMethod: {
@@ -4603,31 +4610,54 @@
 
 void CodeGeneratorX86::RecordBootStringPatch(HLoadString* load_string) {
   DCHECK(GetCompilerOptions().IsBootImage());
-  string_patches_.emplace_back(load_string->GetDexFile(), load_string->GetStringIndex().index_);
+  HX86ComputeBaseMethodAddress* address = nullptr;
+  if (GetCompilerOptions().GetCompilePic()) {
+    address = load_string->InputAt(0)->AsX86ComputeBaseMethodAddress();
+  } else {
+    DCHECK_EQ(load_string->InputCount(), 0u);
+  }
+  string_patches_.emplace_back(address,
+                               load_string->GetDexFile(),
+                               load_string->GetStringIndex().index_);
   __ Bind(&string_patches_.back().label);
 }
 
 void CodeGeneratorX86::RecordBootTypePatch(HLoadClass* load_class) {
-  boot_image_type_patches_.emplace_back(load_class->GetDexFile(),
+  HX86ComputeBaseMethodAddress* address = nullptr;
+  if (GetCompilerOptions().GetCompilePic()) {
+    address = load_class->InputAt(0)->AsX86ComputeBaseMethodAddress();
+  } else {
+    DCHECK_EQ(load_class->InputCount(), 0u);
+  }
+  boot_image_type_patches_.emplace_back(address,
+                                        load_class->GetDexFile(),
                                         load_class->GetTypeIndex().index_);
   __ Bind(&boot_image_type_patches_.back().label);
 }
 
 Label* CodeGeneratorX86::NewTypeBssEntryPatch(HLoadClass* load_class) {
-  type_bss_entry_patches_.emplace_back(load_class->GetDexFile(), load_class->GetTypeIndex().index_);
+  HX86ComputeBaseMethodAddress* address =
+      load_class->InputAt(0)->AsX86ComputeBaseMethodAddress();
+  type_bss_entry_patches_.emplace_back(
+      address, load_class->GetDexFile(), load_class->GetTypeIndex().index_);
   return &type_bss_entry_patches_.back().label;
 }
 
 Label* CodeGeneratorX86::NewStringBssEntryPatch(HLoadString* load_string) {
   DCHECK(!GetCompilerOptions().IsBootImage());
-  string_patches_.emplace_back(load_string->GetDexFile(), load_string->GetStringIndex().index_);
+  HX86ComputeBaseMethodAddress* address =
+      load_string->InputAt(0)->AsX86ComputeBaseMethodAddress();
+  string_patches_.emplace_back(
+      address, load_string->GetDexFile(), load_string->GetStringIndex().index_);
   return &string_patches_.back().label;
 }
 
-Label* CodeGeneratorX86::NewPcRelativeDexCacheArrayPatch(const DexFile& dex_file,
-                                                         uint32_t element_offset) {
+Label* CodeGeneratorX86::NewPcRelativeDexCacheArrayPatch(
+    HX86ComputeBaseMethodAddress* method_address,
+    const DexFile& dex_file,
+    uint32_t element_offset) {
   // Add the patch entry and bind its label at the end of the instruction.
-  pc_relative_dex_cache_patches_.emplace_back(dex_file, element_offset);
+  pc_relative_dex_cache_patches_.emplace_back(method_address, dex_file, element_offset);
   return &pc_relative_dex_cache_patches_.back().label;
 }
 
@@ -4637,12 +4667,12 @@
 
 template <LinkerPatch (*Factory)(size_t, const DexFile*, uint32_t, uint32_t)>
 inline void CodeGeneratorX86::EmitPcRelativeLinkerPatches(
-    const ArenaDeque<PatchInfo<Label>>& infos,
+    const ArenaDeque<X86PcRelativePatchInfo>& infos,
     ArenaVector<LinkerPatch>* linker_patches) {
-  for (const PatchInfo<Label>& info : infos) {
+  for (const X86PcRelativePatchInfo& info : infos) {
     uint32_t literal_offset = info.label.Position() - kLabelPositionToLiteralOffsetAdjustment;
-    linker_patches->push_back(
-        Factory(literal_offset, &info.dex_file, GetMethodAddressOffset(), info.index));
+    linker_patches->push_back(Factory(
+        literal_offset, &info.dex_file, GetMethodAddressOffset(info.method_address), info.index));
   }
 }
 
@@ -6002,13 +6032,6 @@
       FALLTHROUGH_INTENDED;
     case HLoadClass::LoadKind::kBssEntry:
       DCHECK(!Runtime::Current()->UseJitCompilation());  // Note: boot image is also non-JIT.
-      // We disable pc-relative load when there is an irreducible loop, as the optimization
-      // is incompatible with it.
-      // TODO: Create as many X86ComputeBaseMethodAddress instructions as needed for methods
-      // with irreducible loops.
-      if (GetGraph()->HasIrreducibleLoops()) {
-        return HLoadClass::LoadKind::kDexCacheViaMethod;
-      }
       break;
     case HLoadClass::LoadKind::kBootImageAddress:
       break;
@@ -6195,13 +6218,6 @@
       FALLTHROUGH_INTENDED;
     case HLoadString::LoadKind::kBssEntry:
       DCHECK(!Runtime::Current()->UseJitCompilation());  // Note: boot image is also non-JIT.
-      // We disable pc-relative load when there is an irreducible loop, as the optimization
-      // is incompatible with it.
-      // TODO: Create as many X86ComputeBaseMethodAddress instructions as needed for methods
-      // with irreducible loops.
-      if (GetGraph()->HasIrreducibleLoops()) {
-        return HLoadString::LoadKind::kDexCacheViaMethod;
-      }
       break;
     case HLoadString::LoadKind::kBootImageAddress:
       break;
@@ -7489,7 +7505,7 @@
   __ Bind(&next_instruction);
 
   // Remember this offset for later use with constant area.
-  codegen_->SetMethodAddressOffset(GetAssembler()->CodeSize());
+  codegen_->AddMethodAddressOffset(insn, GetAssembler()->CodeSize());
 
   // Grab the return address off the stack.
   __ popl(reg);
@@ -7536,17 +7552,20 @@
   switch (insn->GetType()) {
     case Primitive::kPrimFloat:
       __ movss(out.AsFpuRegister<XmmRegister>(),
-               codegen_->LiteralFloatAddress(value->AsFloatConstant()->GetValue(), const_area));
+               codegen_->LiteralFloatAddress(
+                  value->AsFloatConstant()->GetValue(), insn->GetBaseMethodAddress(), const_area));
       break;
 
     case Primitive::kPrimDouble:
       __ movsd(out.AsFpuRegister<XmmRegister>(),
-               codegen_->LiteralDoubleAddress(value->AsDoubleConstant()->GetValue(), const_area));
+               codegen_->LiteralDoubleAddress(
+                  value->AsDoubleConstant()->GetValue(), insn->GetBaseMethodAddress(), const_area));
       break;
 
     case Primitive::kPrimInt:
       __ movl(out.AsRegister<Register>(),
-              codegen_->LiteralInt32Address(value->AsIntConstant()->GetValue(), const_area));
+              codegen_->LiteralInt32Address(
+                  value->AsIntConstant()->GetValue(), insn->GetBaseMethodAddress(), const_area));
       break;
 
     default:
@@ -7559,13 +7578,18 @@
  */
 class RIPFixup : public AssemblerFixup, public ArenaObject<kArenaAllocCodeGenerator> {
  public:
-  RIPFixup(CodeGeneratorX86& codegen, size_t offset)
-      : codegen_(&codegen), offset_into_constant_area_(offset) {}
+  RIPFixup(CodeGeneratorX86& codegen,
+           HX86ComputeBaseMethodAddress* base_method_address,
+           size_t offset)
+      : codegen_(&codegen),
+        base_method_address_(base_method_address),
+        offset_into_constant_area_(offset) {}
 
  protected:
   void SetOffset(size_t offset) { offset_into_constant_area_ = offset; }
 
   CodeGeneratorX86* codegen_;
+  HX86ComputeBaseMethodAddress* base_method_address_;
 
  private:
   void Process(const MemoryRegion& region, int pos) OVERRIDE {
@@ -7574,7 +7598,8 @@
     // The value to patch is the distance from the offset in the constant area
     // from the address computed by the HX86ComputeBaseMethodAddress instruction.
     int32_t constant_offset = codegen_->ConstantAreaStart() + offset_into_constant_area_;
-    int32_t relative_position = constant_offset - codegen_->GetMethodAddressOffset();
+    int32_t relative_position =
+        constant_offset - codegen_->GetMethodAddressOffset(base_method_address_);
 
     // Patch in the right value.
     region.StoreUnaligned<int32_t>(pos - 4, relative_position);
@@ -7591,7 +7616,8 @@
 class JumpTableRIPFixup : public RIPFixup {
  public:
   JumpTableRIPFixup(CodeGeneratorX86& codegen, HX86PackedSwitch* switch_instr)
-      : RIPFixup(codegen, static_cast<size_t>(-1)), switch_instr_(switch_instr) {}
+      : RIPFixup(codegen, switch_instr->GetBaseMethodAddress(), static_cast<size_t>(-1)),
+        switch_instr_(switch_instr) {}
 
   void CreateJumpTable() {
     X86Assembler* assembler = codegen_->GetAssembler();
@@ -7602,7 +7628,7 @@
 
     // The label values in the jump table are computed relative to the
     // instruction addressing the constant area.
-    const int32_t relative_offset = codegen_->GetMethodAddressOffset();
+    const int32_t relative_offset = codegen_->GetMethodAddressOffset(base_method_address_);
 
     // Populate the jump table with the correct values for the jump table.
     int32_t num_entries = switch_instr_->GetNumEntries();
@@ -7644,23 +7670,32 @@
   CodeGenerator::Finalize(allocator);
 }
 
-Address CodeGeneratorX86::LiteralDoubleAddress(double v, Register reg) {
-  AssemblerFixup* fixup = new (GetGraph()->GetArena()) RIPFixup(*this, __ AddDouble(v));
+Address CodeGeneratorX86::LiteralDoubleAddress(double v,
+                                               HX86ComputeBaseMethodAddress* method_base,
+                                               Register reg) {
+  AssemblerFixup* fixup =
+      new (GetGraph()->GetArena()) RIPFixup(*this, method_base, __ AddDouble(v));
   return Address(reg, kDummy32BitOffset, fixup);
 }
 
-Address CodeGeneratorX86::LiteralFloatAddress(float v, Register reg) {
-  AssemblerFixup* fixup = new (GetGraph()->GetArena()) RIPFixup(*this, __ AddFloat(v));
+Address CodeGeneratorX86::LiteralFloatAddress(float v,
+                                              HX86ComputeBaseMethodAddress* method_base,
+                                              Register reg) {
+  AssemblerFixup* fixup = new (GetGraph()->GetArena()) RIPFixup(*this, method_base, __ AddFloat(v));
   return Address(reg, kDummy32BitOffset, fixup);
 }
 
-Address CodeGeneratorX86::LiteralInt32Address(int32_t v, Register reg) {
-  AssemblerFixup* fixup = new (GetGraph()->GetArena()) RIPFixup(*this, __ AddInt32(v));
+Address CodeGeneratorX86::LiteralInt32Address(int32_t v,
+                                              HX86ComputeBaseMethodAddress* method_base,
+                                              Register reg) {
+  AssemblerFixup* fixup = new (GetGraph()->GetArena()) RIPFixup(*this, method_base, __ AddInt32(v));
   return Address(reg, kDummy32BitOffset, fixup);
 }
 
-Address CodeGeneratorX86::LiteralInt64Address(int64_t v, Register reg) {
-  AssemblerFixup* fixup = new (GetGraph()->GetArena()) RIPFixup(*this, __ AddInt64(v));
+Address CodeGeneratorX86::LiteralInt64Address(int64_t v,
+                                              HX86ComputeBaseMethodAddress* method_base,
+                                              Register reg) {
+  AssemblerFixup* fixup = new (GetGraph()->GetArena()) RIPFixup(*this, method_base, __ AddInt64(v));
   return Address(reg, kDummy32BitOffset, fixup);
 }
 
diff --git a/compiler/optimizing/code_generator_x86.h b/compiler/optimizing/code_generator_x86.h
index 9eb9765..7350fcc 100644
--- a/compiler/optimizing/code_generator_x86.h
+++ b/compiler/optimizing/code_generator_x86.h
@@ -415,7 +415,9 @@
   void RecordBootTypePatch(HLoadClass* load_class);
   Label* NewTypeBssEntryPatch(HLoadClass* load_class);
   Label* NewStringBssEntryPatch(HLoadString* load_string);
-  Label* NewPcRelativeDexCacheArrayPatch(const DexFile& dex_file, uint32_t element_offset);
+  Label* NewPcRelativeDexCacheArrayPatch(HX86ComputeBaseMethodAddress* method_address,
+                                         const DexFile& dex_file,
+                                         uint32_t element_offset);
   Label* NewJitRootStringPatch(const DexFile& dex_file,
                                dex::StringIndex dex_index,
                                Handle<mirror::String> handle);
@@ -463,22 +465,22 @@
     return isa_features_;
   }
 
-  void SetMethodAddressOffset(int32_t offset) {
-    method_address_offset_ = offset;
+  void AddMethodAddressOffset(HX86ComputeBaseMethodAddress* method_base, int32_t offset) {
+    method_address_offset_.Put(method_base->GetId(), offset);
   }
 
-  int32_t GetMethodAddressOffset() const {
-    return method_address_offset_;
+  int32_t GetMethodAddressOffset(HX86ComputeBaseMethodAddress* method_base) const {
+    return method_address_offset_.Get(method_base->GetId());
   }
 
   int32_t ConstantAreaStart() const {
     return constant_area_start_;
   }
 
-  Address LiteralDoubleAddress(double v, Register reg);
-  Address LiteralFloatAddress(float v, Register reg);
-  Address LiteralInt32Address(int32_t v, Register reg);
-  Address LiteralInt64Address(int64_t v, Register reg);
+  Address LiteralDoubleAddress(double v, HX86ComputeBaseMethodAddress* method_base, Register reg);
+  Address LiteralFloatAddress(float v, HX86ComputeBaseMethodAddress* method_base, Register reg);
+  Address LiteralInt32Address(int32_t v, HX86ComputeBaseMethodAddress* method_base, Register reg);
+  Address LiteralInt64Address(int64_t v, HX86ComputeBaseMethodAddress* method_base, Register reg);
 
   // Load a 32-bit value into a register in the most efficient manner.
   void Load32BitValue(Register dest, int32_t value);
@@ -603,12 +605,21 @@
   static constexpr int32_t kDummy32BitOffset = 256;
 
  private:
-  Register GetInvokeStaticOrDirectExtraParameter(HInvokeStaticOrDirect* invoke, Register temp);
+  struct X86PcRelativePatchInfo : PatchInfo<Label> {
+    X86PcRelativePatchInfo(HX86ComputeBaseMethodAddress* address,
+                           const DexFile& target_dex_file,
+                           uint32_t target_index)
+        : PatchInfo(target_dex_file, target_index),
+          method_address(address) {}
+    HX86ComputeBaseMethodAddress* method_address;
+  };
 
   template <LinkerPatch (*Factory)(size_t, const DexFile*, uint32_t, uint32_t)>
-  void EmitPcRelativeLinkerPatches(const ArenaDeque<PatchInfo<Label>>& infos,
+  void EmitPcRelativeLinkerPatches(const ArenaDeque<X86PcRelativePatchInfo>& infos,
                                    ArenaVector<LinkerPatch>* linker_patches);
 
+  Register GetInvokeStaticOrDirectExtraParameter(HInvokeStaticOrDirect* invoke, Register temp);
+
   // Labels for each block that will be compiled.
   Label* block_labels_;  // Indexed by block id.
   Label frame_entry_label_;
@@ -619,15 +630,15 @@
   const X86InstructionSetFeatures& isa_features_;
 
   // PC-relative DexCache access info.
-  ArenaDeque<PatchInfo<Label>> pc_relative_dex_cache_patches_;
+  ArenaDeque<X86PcRelativePatchInfo> pc_relative_dex_cache_patches_;
   // Patch locations for patchoat where the linker doesn't do any other work.
   ArenaDeque<Label> simple_patches_;
   // String patch locations; type depends on configuration (app .bss or boot image PIC/non-PIC).
-  ArenaDeque<PatchInfo<Label>> string_patches_;
+  ArenaDeque<X86PcRelativePatchInfo> string_patches_;
   // Type patch locations for boot image; type depends on configuration (boot image PIC/non-PIC).
-  ArenaDeque<PatchInfo<Label>> boot_image_type_patches_;
+  ArenaDeque<X86PcRelativePatchInfo> boot_image_type_patches_;
   // Type patch locations for kBssEntry.
-  ArenaDeque<PatchInfo<Label>> type_bss_entry_patches_;
+  ArenaDeque<X86PcRelativePatchInfo> type_bss_entry_patches_;
 
   // Patches for string root accesses in JIT compiled code.
   ArenaDeque<PatchInfo<Label>> jit_string_patches_;
@@ -642,11 +653,9 @@
   // Fixups for jump tables that need to be patched after the constant table is generated.
   ArenaVector<JumpTableRIPFixup*> fixups_to_jump_tables_;
 
-  // If there is a HX86ComputeBaseMethodAddress instruction in the graph
-  // (which shall be the sole instruction of this kind), subtracting this offset
-  // from the value contained in the out register of this HX86ComputeBaseMethodAddress
-  // instruction gives the address of the start of this method.
-  int32_t method_address_offset_;
+  // Maps a HX86ComputeBaseMethodAddress instruction id, to its offset in the
+  // compiled code.
+  ArenaSafeMap<uint32_t, int32_t> method_address_offset_;
 
   DISALLOW_COPY_AND_ASSIGN(CodeGeneratorX86);
 };
diff --git a/compiler/optimizing/code_generator_x86_64.cc b/compiler/optimizing/code_generator_x86_64.cc
index 74c71cc..c4caf4b 100644
--- a/compiler/optimizing/code_generator_x86_64.cc
+++ b/compiler/optimizing/code_generator_x86_64.cc
@@ -1809,7 +1809,7 @@
         cond = X86_64IntegerCondition(condition->GetCondition());
       }
     } else {
-      // Must be a boolean condition, which needs to be compared to 0.
+      // Must be a Boolean condition, which needs to be compared to 0.
       CpuRegister cond_reg = locations->InAt(2).AsRegister<CpuRegister>();
       __ testl(cond_reg, cond_reg);
     }
@@ -4210,7 +4210,7 @@
 
 void CodeGeneratorX86_64::GenerateMemoryBarrier(MemBarrierKind kind) {
   /*
-   * According to the JSR-133 Cookbook, for x86 only StoreLoad/AnyAny barriers need memory fence.
+   * According to the JSR-133 Cookbook, for x86-64 only StoreLoad/AnyAny barriers need memory fence.
    * All other barriers (LoadAny, AnyStore, StoreStore) are nops due to the x86-64 memory model.
    * For those cases, all we need to ensure is that there is a scheduling barrier in place.
    */
diff --git a/compiler/optimizing/dex_cache_array_fixups_arm.cc b/compiler/optimizing/dex_cache_array_fixups_arm.cc
index 9ddcd56..cfcb276 100644
--- a/compiler/optimizing/dex_cache_array_fixups_arm.cc
+++ b/compiler/optimizing/dex_cache_array_fixups_arm.cc
@@ -65,7 +65,7 @@
     if (invoke->HasPcRelativeDexCache() &&
         !IsCallFreeIntrinsic<IntrinsicLocationsBuilderARMType>(invoke, codegen_)) {
       HArmDexCacheArraysBase* base =
-          GetOrCreateDexCacheArrayBase(invoke->GetDexFileForPcRelativeDexCache());
+          GetOrCreateDexCacheArrayBase(invoke, invoke->GetDexFileForPcRelativeDexCache());
       // Update the element offset in base.
       DexCacheArraysLayout layout(kArmPointerSize, &invoke->GetDexFileForPcRelativeDexCache());
       base->UpdateElementOffset(layout.MethodOffset(invoke->GetDexMethodIndex()));
@@ -75,21 +75,28 @@
     }
   }
 
-  HArmDexCacheArraysBase* GetOrCreateDexCacheArrayBase(const DexFile& dex_file) {
-    // Ensure we only initialize the pointer once for each dex file.
-    auto lb = dex_cache_array_bases_.lower_bound(&dex_file);
-    if (lb != dex_cache_array_bases_.end() &&
-        !dex_cache_array_bases_.key_comp()(&dex_file, lb->first)) {
-      return lb->second;
-    }
+  HArmDexCacheArraysBase* GetOrCreateDexCacheArrayBase(HInstruction* cursor,
+                                                       const DexFile& dex_file) {
+    if (GetGraph()->HasIrreducibleLoops()) {
+      HArmDexCacheArraysBase* base = new (GetGraph()->GetArena()) HArmDexCacheArraysBase(dex_file);
+      cursor->GetBlock()->InsertInstructionBefore(base, cursor);
+      return base;
+    } else {
+      // Ensure we only initialize the pointer once for each dex file.
+      auto lb = dex_cache_array_bases_.lower_bound(&dex_file);
+      if (lb != dex_cache_array_bases_.end() &&
+          !dex_cache_array_bases_.key_comp()(&dex_file, lb->first)) {
+        return lb->second;
+      }
 
-    // Insert the base at the start of the entry block, move it to a better
-    // position later in MoveBaseIfNeeded().
-    HArmDexCacheArraysBase* base = new (GetGraph()->GetArena()) HArmDexCacheArraysBase(dex_file);
-    HBasicBlock* entry_block = GetGraph()->GetEntryBlock();
-    entry_block->InsertInstructionBefore(base, entry_block->GetFirstInstruction());
-    dex_cache_array_bases_.PutBefore(lb, &dex_file, base);
-    return base;
+      // Insert the base at the start of the entry block, move it to a better
+      // position later in MoveBaseIfNeeded().
+      HArmDexCacheArraysBase* base = new (GetGraph()->GetArena()) HArmDexCacheArraysBase(dex_file);
+      HBasicBlock* entry_block = GetGraph()->GetEntryBlock();
+      entry_block->InsertInstructionBefore(base, entry_block->GetFirstInstruction());
+      dex_cache_array_bases_.PutBefore(lb, &dex_file, base);
+      return base;
+    }
   }
 
   CodeGeneratorARMType* codegen_;
@@ -100,11 +107,6 @@
 };
 
 void DexCacheArrayFixups::Run() {
-  if (graph_->HasIrreducibleLoops()) {
-    // Do not run this optimization, as irreducible loops do not work with an instruction
-    // that can be live-in at the irreducible loop header.
-    return;
-  }
   DexCacheArrayFixupsVisitor visitor(graph_, codegen_);
   visitor.VisitInsertionOrder();
   visitor.MoveBasesIfNeeded();
diff --git a/compiler/optimizing/gvn.cc b/compiler/optimizing/gvn.cc
index f5931a2..c93bc21 100644
--- a/compiler/optimizing/gvn.cc
+++ b/compiler/optimizing/gvn.cc
@@ -399,7 +399,7 @@
   ArenaVector<ValueSet*> sets_;
 
   // BitVector which serves as a fast-access map from block id to
-  // visited/unvisited boolean.
+  // visited/unvisited Boolean.
   ArenaBitVector visited_blocks_;
 
   DISALLOW_COPY_AND_ASSIGN(GlobalValueNumberer);
diff --git a/compiler/optimizing/instruction_builder.cc b/compiler/optimizing/instruction_builder.cc
index ef8d74d..cac385c 100644
--- a/compiler/optimizing/instruction_builder.cc
+++ b/compiler/optimizing/instruction_builder.cc
@@ -1,4 +1,3 @@
-
 /*
  * Copyright (C) 2016 The Android Open Source Project
  *
diff --git a/compiler/optimizing/intrinsics_arm.cc b/compiler/optimizing/intrinsics_arm.cc
index 8f64fae..c262cf9 100644
--- a/compiler/optimizing/intrinsics_arm.cc
+++ b/compiler/optimizing/intrinsics_arm.cc
@@ -2592,6 +2592,58 @@
   __ Lsr(out, out, 5);
 }
 
+void IntrinsicLocationsBuilderARM::VisitReferenceGetReferent(HInvoke* invoke) {
+  if (kEmitCompilerReadBarrier) {
+    // Do not intrinsify this call with the read barrier configuration.
+    return;
+  }
+  LocationSummary* locations = new (arena_) LocationSummary(invoke,
+                                                            LocationSummary::kCallOnSlowPath,
+                                                            kIntrinsified);
+  locations->SetInAt(0, Location::RequiresRegister());
+  locations->SetOut(Location::SameAsFirstInput());
+  locations->AddTemp(Location::RequiresRegister());
+}
+
+void IntrinsicCodeGeneratorARM::VisitReferenceGetReferent(HInvoke* invoke) {
+  DCHECK(!kEmitCompilerReadBarrier);
+  ArmAssembler* const assembler = GetAssembler();
+  LocationSummary* locations = invoke->GetLocations();
+
+  Register obj = locations->InAt(0).AsRegister<Register>();
+  Register out = locations->Out().AsRegister<Register>();
+
+  SlowPathCode* slow_path = new (GetAllocator()) IntrinsicSlowPathARM(invoke);
+  codegen_->AddSlowPath(slow_path);
+
+  // Load ArtMethod first.
+  HInvokeStaticOrDirect* invoke_direct = invoke->AsInvokeStaticOrDirect();
+  DCHECK(invoke_direct != nullptr);
+  Register temp = codegen_->GenerateCalleeMethodStaticOrDirectCall(
+      invoke_direct, locations->GetTemp(0)).AsRegister<Register>();
+
+  // Now get declaring class.
+  __ ldr(temp, Address(temp, ArtMethod::DeclaringClassOffset().Int32Value()));
+
+  uint32_t slow_path_flag_offset = codegen_->GetReferenceSlowFlagOffset();
+  uint32_t disable_flag_offset = codegen_->GetReferenceDisableFlagOffset();
+  DCHECK_NE(slow_path_flag_offset, 0u);
+  DCHECK_NE(disable_flag_offset, 0u);
+  DCHECK_NE(slow_path_flag_offset, disable_flag_offset);
+
+  // Check static flags that prevent using intrinsic.
+  __ ldr(IP, Address(temp, disable_flag_offset));
+  __ ldr(temp, Address(temp, slow_path_flag_offset));
+  __ orr(IP, IP, ShifterOperand(temp));
+  __ CompareAndBranchIfNonZero(IP, slow_path->GetEntryLabel());
+
+  // Fast path.
+  __ ldr(out, Address(obj, mirror::Reference::ReferentOffset().Int32Value()));
+  codegen_->MaybeRecordImplicitNullCheck(invoke);
+  __ MaybeUnpoisonHeapReference(out);
+  __ Bind(slow_path->GetExitLabel());
+}
+
 UNIMPLEMENTED_INTRINSIC(ARM, MathMinDoubleDouble)
 UNIMPLEMENTED_INTRINSIC(ARM, MathMinFloatFloat)
 UNIMPLEMENTED_INTRINSIC(ARM, MathMaxDoubleDouble)
@@ -2605,7 +2657,6 @@
 UNIMPLEMENTED_INTRINSIC(ARM, MathRoundFloat)    // Could be done by changing rounding mode, maybe?
 UNIMPLEMENTED_INTRINSIC(ARM, UnsafeCASLong)     // High register pressure.
 UNIMPLEMENTED_INTRINSIC(ARM, SystemArrayCopyChar)
-UNIMPLEMENTED_INTRINSIC(ARM, ReferenceGetReferent)
 UNIMPLEMENTED_INTRINSIC(ARM, IntegerHighestOneBit)
 UNIMPLEMENTED_INTRINSIC(ARM, LongHighestOneBit)
 UNIMPLEMENTED_INTRINSIC(ARM, IntegerLowestOneBit)
diff --git a/compiler/optimizing/intrinsics_arm64.cc b/compiler/optimizing/intrinsics_arm64.cc
index d8a896e..bbf826c 100644
--- a/compiler/optimizing/intrinsics_arm64.cc
+++ b/compiler/optimizing/intrinsics_arm64.cc
@@ -2773,7 +2773,65 @@
   GenIsInfinite(invoke->GetLocations(), /* is64bit */ true, GetVIXLAssembler());
 }
 
-UNIMPLEMENTED_INTRINSIC(ARM64, ReferenceGetReferent)
+void IntrinsicLocationsBuilderARM64::VisitReferenceGetReferent(HInvoke* invoke) {
+  if (kEmitCompilerReadBarrier) {
+    // Do not intrinsify this call with the read barrier configuration.
+    return;
+  }
+  LocationSummary* locations = new (arena_) LocationSummary(invoke,
+                                                            LocationSummary::kCallOnSlowPath,
+                                                            kIntrinsified);
+  locations->SetInAt(0, Location::RequiresRegister());
+  locations->SetOut(Location::SameAsFirstInput());
+  locations->AddTemp(Location::RequiresRegister());
+}
+
+void IntrinsicCodeGeneratorARM64::VisitReferenceGetReferent(HInvoke* invoke) {
+  DCHECK(!kEmitCompilerReadBarrier);
+  MacroAssembler* masm = GetVIXLAssembler();
+  LocationSummary* locations = invoke->GetLocations();
+
+  Register obj = InputRegisterAt(invoke, 0);
+  Register out = OutputRegister(invoke);
+
+  SlowPathCodeARM64* slow_path = new (GetAllocator()) IntrinsicSlowPathARM64(invoke);
+  codegen_->AddSlowPath(slow_path);
+
+  // Load ArtMethod first.
+  HInvokeStaticOrDirect* invoke_direct = invoke->AsInvokeStaticOrDirect();
+  DCHECK(invoke_direct != nullptr);
+  Register temp0 = XRegisterFrom(codegen_->GenerateCalleeMethodStaticOrDirectCall(
+                                 invoke_direct, locations->GetTemp(0)));
+
+  // Now get declaring class.
+  __ Ldr(temp0.W(), MemOperand(temp0, ArtMethod::DeclaringClassOffset().Int32Value()));
+
+  uint32_t slow_path_flag_offset = codegen_->GetReferenceSlowFlagOffset();
+  uint32_t disable_flag_offset = codegen_->GetReferenceDisableFlagOffset();
+  DCHECK_NE(slow_path_flag_offset, 0u);
+  DCHECK_NE(disable_flag_offset, 0u);
+  DCHECK_NE(slow_path_flag_offset, disable_flag_offset);
+
+  // Check static flags that prevent using intrinsic.
+  if (slow_path_flag_offset == disable_flag_offset + 1) {
+    // Load two adjacent flags in one 64-bit load.
+    __ Ldr(temp0, MemOperand(temp0, disable_flag_offset));
+  } else {
+    UseScratchRegisterScope temps(masm);
+    Register temp1 = temps.AcquireW();
+    __ Ldr(temp1.W(), MemOperand(temp0, disable_flag_offset));
+    __ Ldr(temp0.W(), MemOperand(temp0, slow_path_flag_offset));
+    __ Orr(temp0, temp1, temp0);
+  }
+  __ Cbnz(temp0, slow_path->GetEntryLabel());
+
+  // Fast path.
+  __ Ldr(out, HeapOperand(obj, mirror::Reference::ReferentOffset().Int32Value()));
+  codegen_->MaybeRecordImplicitNullCheck(invoke);
+  codegen_->GetAssembler()->MaybeUnpoisonHeapReference(out);
+  __ Bind(slow_path->GetExitLabel());
+}
+
 UNIMPLEMENTED_INTRINSIC(ARM64, IntegerHighestOneBit)
 UNIMPLEMENTED_INTRINSIC(ARM64, LongHighestOneBit)
 UNIMPLEMENTED_INTRINSIC(ARM64, IntegerLowestOneBit)
diff --git a/compiler/optimizing/intrinsics_arm_vixl.cc b/compiler/optimizing/intrinsics_arm_vixl.cc
index 85e84d8..68c2d2e 100644
--- a/compiler/optimizing/intrinsics_arm_vixl.cc
+++ b/compiler/optimizing/intrinsics_arm_vixl.cc
@@ -2688,6 +2688,60 @@
   __ Lsr(out, out, 5);
 }
 
+void IntrinsicLocationsBuilderARMVIXL::VisitReferenceGetReferent(HInvoke* invoke) {
+  if (kEmitCompilerReadBarrier) {
+    // Do not intrinsify this call with the read barrier configuration.
+    return;
+  }
+  LocationSummary* locations = new (arena_) LocationSummary(invoke,
+                                                            LocationSummary::kCallOnSlowPath,
+                                                            kIntrinsified);
+  locations->SetInAt(0, Location::RequiresRegister());
+  locations->SetOut(Location::SameAsFirstInput());
+  locations->AddTemp(Location::RequiresRegister());
+}
+
+void IntrinsicCodeGeneratorARMVIXL::VisitReferenceGetReferent(HInvoke* invoke) {
+  DCHECK(!kEmitCompilerReadBarrier);
+  ArmVIXLAssembler* assembler = GetAssembler();
+  LocationSummary* locations = invoke->GetLocations();
+
+  vixl32::Register obj = InputRegisterAt(invoke, 0);
+  vixl32::Register out = OutputRegister(invoke);
+
+  SlowPathCodeARMVIXL* slow_path = new (GetAllocator()) IntrinsicSlowPathARMVIXL(invoke);
+  codegen_->AddSlowPath(slow_path);
+
+  // Load ArtMethod first.
+  HInvokeStaticOrDirect* invoke_direct = invoke->AsInvokeStaticOrDirect();
+  DCHECK(invoke_direct != nullptr);
+  vixl32::Register temp0 = RegisterFrom(codegen_->GenerateCalleeMethodStaticOrDirectCall(
+      invoke_direct, locations->GetTemp(0)));
+
+  // Now get declaring class.
+  __ Ldr(temp0, MemOperand(temp0, ArtMethod::DeclaringClassOffset().Int32Value()));
+
+  uint32_t slow_path_flag_offset = codegen_->GetReferenceSlowFlagOffset();
+  uint32_t disable_flag_offset = codegen_->GetReferenceDisableFlagOffset();
+  DCHECK_NE(slow_path_flag_offset, 0u);
+  DCHECK_NE(disable_flag_offset, 0u);
+  DCHECK_NE(slow_path_flag_offset, disable_flag_offset);
+
+  // Check static flags that prevent using intrinsic.
+  UseScratchRegisterScope temps(assembler->GetVIXLAssembler());
+  vixl32::Register temp1 = temps.Acquire();
+  __ Ldr(temp1, MemOperand(temp0, disable_flag_offset));
+  __ Ldr(temp0, MemOperand(temp0, slow_path_flag_offset));
+  __ Orr(temp0, temp1, temp0);
+  __ CompareAndBranchIfNonZero(temp0, slow_path->GetEntryLabel());
+
+  // Fast path.
+  __ Ldr(out, MemOperand(obj, mirror::Reference::ReferentOffset().Int32Value()));
+  codegen_->MaybeRecordImplicitNullCheck(invoke);
+  assembler->MaybeUnpoisonHeapReference(out);
+  __ Bind(slow_path->GetExitLabel());
+}
+
 UNIMPLEMENTED_INTRINSIC(ARMVIXL, MathMinDoubleDouble)
 UNIMPLEMENTED_INTRINSIC(ARMVIXL, MathMinFloatFloat)
 UNIMPLEMENTED_INTRINSIC(ARMVIXL, MathMaxDoubleDouble)
@@ -2701,7 +2755,6 @@
 UNIMPLEMENTED_INTRINSIC(ARMVIXL, MathRoundFloat)    // Could be done by changing rounding mode, maybe?
 UNIMPLEMENTED_INTRINSIC(ARMVIXL, UnsafeCASLong)     // High register pressure.
 UNIMPLEMENTED_INTRINSIC(ARMVIXL, SystemArrayCopyChar)
-UNIMPLEMENTED_INTRINSIC(ARMVIXL, ReferenceGetReferent)
 UNIMPLEMENTED_INTRINSIC(ARMVIXL, IntegerHighestOneBit)
 UNIMPLEMENTED_INTRINSIC(ARMVIXL, LongHighestOneBit)
 UNIMPLEMENTED_INTRINSIC(ARMVIXL, IntegerLowestOneBit)
diff --git a/compiler/optimizing/intrinsics_mips.cc b/compiler/optimizing/intrinsics_mips.cc
index f1ae549..6cf9b83 100644
--- a/compiler/optimizing/intrinsics_mips.cc
+++ b/compiler/optimizing/intrinsics_mips.cc
@@ -1878,7 +1878,7 @@
                         // If we use 'value' directly, we would lose 'value'
                         // in the case that the store fails.  Whether the
                         // store succeeds, or fails, it will load the
-                        // correct boolean value into the 'out' register.
+                        // correct Boolean value into the 'out' register.
   // This test isn't really necessary. We only support Primitive::kPrimInt,
   // Primitive::kPrimNot, and we already verified that we're working on one
   // of those two types. It's left here in case the code needs to support
diff --git a/compiler/optimizing/intrinsics_mips64.cc b/compiler/optimizing/intrinsics_mips64.cc
index 3022e97..00a1fa1 100644
--- a/compiler/optimizing/intrinsics_mips64.cc
+++ b/compiler/optimizing/intrinsics_mips64.cc
@@ -1477,7 +1477,7 @@
                         // If we use 'value' directly, we would lose 'value'
                         // in the case that the store fails.  Whether the
                         // store succeeds, or fails, it will load the
-                        // correct boolean value into the 'out' register.
+                        // correct Boolean value into the 'out' register.
   if (type == Primitive::kPrimLong) {
     __ Scd(out, TMP);
   } else {
diff --git a/compiler/optimizing/intrinsics_x86.cc b/compiler/optimizing/intrinsics_x86.cc
index 922c3bc..e1b7ea5 100644
--- a/compiler/optimizing/intrinsics_x86.cc
+++ b/compiler/optimizing/intrinsics_x86.cc
@@ -356,23 +356,28 @@
   }
 }
 
-static void MathAbsFP(LocationSummary* locations,
+static void MathAbsFP(HInvoke* invoke,
                       bool is64bit,
                       X86Assembler* assembler,
                       CodeGeneratorX86* codegen) {
+  LocationSummary* locations = invoke->GetLocations();
   Location output = locations->Out();
 
   DCHECK(output.IsFpuRegister());
   if (locations->GetInputCount() == 2 && locations->InAt(1).IsValid()) {
+    HX86ComputeBaseMethodAddress* method_address =
+        invoke->InputAt(1)->AsX86ComputeBaseMethodAddress();
     DCHECK(locations->InAt(1).IsRegister());
     // We also have a constant area pointer.
     Register constant_area = locations->InAt(1).AsRegister<Register>();
     XmmRegister temp = locations->GetTemp(0).AsFpuRegister<XmmRegister>();
     if (is64bit) {
-      __ movsd(temp, codegen->LiteralInt64Address(INT64_C(0x7FFFFFFFFFFFFFFF), constant_area));
+      __ movsd(temp, codegen->LiteralInt64Address(
+          INT64_C(0x7FFFFFFFFFFFFFFF), method_address, constant_area));
       __ andpd(output.AsFpuRegister<XmmRegister>(), temp);
     } else {
-      __ movss(temp, codegen->LiteralInt32Address(INT32_C(0x7FFFFFFF), constant_area));
+      __ movss(temp, codegen->LiteralInt32Address(
+          INT32_C(0x7FFFFFFF), method_address, constant_area));
       __ andps(output.AsFpuRegister<XmmRegister>(), temp);
     }
   } else {
@@ -396,7 +401,7 @@
 }
 
 void IntrinsicCodeGeneratorX86::VisitMathAbsDouble(HInvoke* invoke) {
-  MathAbsFP(invoke->GetLocations(), /* is64bit */ true, GetAssembler(), codegen_);
+  MathAbsFP(invoke, /* is64bit */ true, GetAssembler(), codegen_);
 }
 
 void IntrinsicLocationsBuilderX86::VisitMathAbsFloat(HInvoke* invoke) {
@@ -404,7 +409,7 @@
 }
 
 void IntrinsicCodeGeneratorX86::VisitMathAbsFloat(HInvoke* invoke) {
-  MathAbsFP(invoke->GetLocations(), /* is64bit */ false, GetAssembler(), codegen_);
+  MathAbsFP(invoke, /* is64bit */ false, GetAssembler(), codegen_);
 }
 
 static void CreateAbsIntLocation(ArenaAllocator* arena, HInvoke* invoke) {
@@ -486,11 +491,12 @@
   GenAbsLong(invoke->GetLocations(), GetAssembler());
 }
 
-static void GenMinMaxFP(LocationSummary* locations,
+static void GenMinMaxFP(HInvoke* invoke,
                         bool is_min,
                         bool is_double,
                         X86Assembler* assembler,
                         CodeGeneratorX86* codegen) {
+  LocationSummary* locations = invoke->GetLocations();
   Location op1_loc = locations->InAt(0);
   Location op2_loc = locations->InAt(1);
   Location out_loc = locations->Out();
@@ -553,12 +559,14 @@
   __ Bind(&nan);
   // Do we have a constant area pointer?
   if (locations->GetInputCount() == 3 && locations->InAt(2).IsValid()) {
+    HX86ComputeBaseMethodAddress* method_address =
+        invoke->InputAt(2)->AsX86ComputeBaseMethodAddress();
     DCHECK(locations->InAt(2).IsRegister());
     Register constant_area = locations->InAt(2).AsRegister<Register>();
     if (is_double) {
-      __ movsd(out, codegen->LiteralInt64Address(kDoubleNaN, constant_area));
+      __ movsd(out, codegen->LiteralInt64Address(kDoubleNaN, method_address, constant_area));
     } else {
-      __ movss(out, codegen->LiteralInt32Address(kFloatNaN, constant_area));
+      __ movss(out, codegen->LiteralInt32Address(kFloatNaN, method_address, constant_area));
     }
   } else {
     if (is_double) {
@@ -608,7 +616,7 @@
 }
 
 void IntrinsicCodeGeneratorX86::VisitMathMinDoubleDouble(HInvoke* invoke) {
-  GenMinMaxFP(invoke->GetLocations(),
+  GenMinMaxFP(invoke,
               /* is_min */ true,
               /* is_double */ true,
               GetAssembler(),
@@ -620,7 +628,7 @@
 }
 
 void IntrinsicCodeGeneratorX86::VisitMathMinFloatFloat(HInvoke* invoke) {
-  GenMinMaxFP(invoke->GetLocations(),
+  GenMinMaxFP(invoke,
               /* is_min */ true,
               /* is_double */ false,
               GetAssembler(),
@@ -632,7 +640,7 @@
 }
 
 void IntrinsicCodeGeneratorX86::VisitMathMaxDoubleDouble(HInvoke* invoke) {
-  GenMinMaxFP(invoke->GetLocations(),
+  GenMinMaxFP(invoke,
               /* is_min */ false,
               /* is_double */ true,
               GetAssembler(),
@@ -644,7 +652,7 @@
 }
 
 void IntrinsicCodeGeneratorX86::VisitMathMaxFloatFloat(HInvoke* invoke) {
-  GenMinMaxFP(invoke->GetLocations(),
+  GenMinMaxFP(invoke,
               /* is_min */ false,
               /* is_double */ false,
               GetAssembler(),
@@ -905,10 +913,16 @@
   __ subss(t2, t1);
   if (locations->GetInputCount() == 2 && locations->InAt(1).IsValid()) {
     // Direct constant area available.
+    HX86ComputeBaseMethodAddress* method_address =
+        invoke->InputAt(1)->AsX86ComputeBaseMethodAddress();
     Register constant_area = locations->InAt(1).AsRegister<Register>();
-    __ comiss(t2, codegen_->LiteralInt32Address(bit_cast<int32_t, float>(0.5f), constant_area));
+    __ comiss(t2, codegen_->LiteralInt32Address(bit_cast<int32_t, float>(0.5f),
+                                                method_address,
+                                                constant_area));
     __ j(kBelow, &skip_incr);
-    __ addss(t1, codegen_->LiteralInt32Address(bit_cast<int32_t, float>(1.0f), constant_area));
+    __ addss(t1, codegen_->LiteralInt32Address(bit_cast<int32_t, float>(1.0f),
+                                               method_address,
+                                               constant_area));
     __ Bind(&skip_incr);
   } else {
     // No constant area: go through stack.
diff --git a/compiler/optimizing/nodes.h b/compiler/optimizing/nodes.h
index a2980dc..f0ea9e2 100644
--- a/compiler/optimizing/nodes.h
+++ b/compiler/optimizing/nodes.h
@@ -565,7 +565,7 @@
   ArtMethod* GetArtMethod() const { return art_method_; }
   void SetArtMethod(ArtMethod* method) { art_method_ = method; }
 
-  // Returns an instruction with the opposite boolean value from 'cond'.
+  // Returns an instruction with the opposite Boolean value from 'cond'.
   // The instruction has been inserted into the graph, either as a constant, or
   // before cursor.
   HInstruction* InsertOppositeCondition(HInstruction* cond, HInstruction* cursor);
diff --git a/compiler/optimizing/nodes_x86.h b/compiler/optimizing/nodes_x86.h
index fa47976..75893c3 100644
--- a/compiler/optimizing/nodes_x86.h
+++ b/compiler/optimizing/nodes_x86.h
@@ -71,6 +71,10 @@
     SetRawInputAt(1, method_base);
   }
 
+  HX86ComputeBaseMethodAddress* GetBaseMethodAddress() const {
+    return InputAt(1)->AsX86ComputeBaseMethodAddress();
+  }
+
   DECLARE_INSTRUCTION(X86FPNeg);
 
  private:
diff --git a/compiler/optimizing/pc_relative_fixups_x86.cc b/compiler/optimizing/pc_relative_fixups_x86.cc
index 2befc8c..a1c916f 100644
--- a/compiler/optimizing/pc_relative_fixups_x86.cc
+++ b/compiler/optimizing/pc_relative_fixups_x86.cc
@@ -84,8 +84,8 @@
     HLoadClass::LoadKind load_kind = load_class->GetLoadKind();
     if (load_kind == HLoadClass::LoadKind::kBootImageLinkTimePcRelative ||
         load_kind == HLoadClass::LoadKind::kBssEntry) {
-      InitializePCRelativeBasePointer();
-      load_class->AddSpecialInput(base_);
+      HX86ComputeBaseMethodAddress* method_address = GetPCRelativeBasePointer(load_class);
+      load_class->AddSpecialInput(method_address);
     }
   }
 
@@ -93,8 +93,8 @@
     HLoadString::LoadKind load_kind = load_string->GetLoadKind();
     if (load_kind == HLoadString::LoadKind::kBootImageLinkTimePcRelative ||
         load_kind == HLoadString::LoadKind::kBssEntry) {
-      InitializePCRelativeBasePointer();
-      load_string->AddSpecialInput(base_);
+      HX86ComputeBaseMethodAddress* method_address = GetPCRelativeBasePointer(load_string);
+      load_string->AddSpecialInput(method_address);
     }
   }
 
@@ -132,13 +132,13 @@
   void VisitNeg(HNeg* neg) OVERRIDE {
     if (Primitive::IsFloatingPointType(neg->GetType())) {
       // We need to replace the HNeg with a HX86FPNeg in order to address the constant area.
-      InitializePCRelativeBasePointer();
+      HX86ComputeBaseMethodAddress* method_address = GetPCRelativeBasePointer(neg);
       HGraph* graph = GetGraph();
       HBasicBlock* block = neg->GetBlock();
       HX86FPNeg* x86_fp_neg = new (graph->GetArena()) HX86FPNeg(
           neg->GetType(),
           neg->InputAt(0),
-          base_,
+          method_address,
           neg->GetDexPc());
       block->ReplaceAndRemoveInstructionWith(neg, x86_fp_neg);
     }
@@ -151,35 +151,44 @@
     }
     // We need to replace the HPackedSwitch with a HX86PackedSwitch in order to
     // address the constant area.
-    InitializePCRelativeBasePointer();
+    HX86ComputeBaseMethodAddress* method_address = GetPCRelativeBasePointer(switch_insn);
     HGraph* graph = GetGraph();
     HBasicBlock* block = switch_insn->GetBlock();
     HX86PackedSwitch* x86_switch = new (graph->GetArena()) HX86PackedSwitch(
         switch_insn->GetStartValue(),
         switch_insn->GetNumEntries(),
         switch_insn->InputAt(0),
-        base_,
+        method_address,
         switch_insn->GetDexPc());
     block->ReplaceAndRemoveInstructionWith(switch_insn, x86_switch);
   }
 
-  void InitializePCRelativeBasePointer() {
-    // Ensure we only initialize the pointer once.
-    if (base_ != nullptr) {
-      return;
+  HX86ComputeBaseMethodAddress* GetPCRelativeBasePointer(HInstruction* cursor) {
+    bool has_irreducible_loops = GetGraph()->HasIrreducibleLoops();
+    if (!has_irreducible_loops) {
+      // Ensure we only initialize the pointer once.
+      if (base_ != nullptr) {
+        return base_;
+      }
     }
     // Insert the base at the start of the entry block, move it to a better
     // position later in MoveBaseIfNeeded().
-    base_ = new (GetGraph()->GetArena()) HX86ComputeBaseMethodAddress();
-    HBasicBlock* entry_block = GetGraph()->GetEntryBlock();
-    entry_block->InsertInstructionBefore(base_, entry_block->GetFirstInstruction());
-    DCHECK(base_ != nullptr);
+    HX86ComputeBaseMethodAddress* method_address =
+        new (GetGraph()->GetArena()) HX86ComputeBaseMethodAddress();
+    if (has_irreducible_loops) {
+      cursor->GetBlock()->InsertInstructionBefore(method_address, cursor);
+    } else {
+      HBasicBlock* entry_block = GetGraph()->GetEntryBlock();
+      entry_block->InsertInstructionBefore(method_address, entry_block->GetFirstInstruction());
+      base_ = method_address;
+    }
+    return method_address;
   }
 
   void ReplaceInput(HInstruction* insn, HConstant* value, int input_index, bool materialize) {
-    InitializePCRelativeBasePointer();
+    HX86ComputeBaseMethodAddress* method_address = GetPCRelativeBasePointer(insn);
     HX86LoadFromConstantTable* load_constant =
-        new (GetGraph()->GetArena()) HX86LoadFromConstantTable(base_, value);
+        new (GetGraph()->GetArena()) HX86LoadFromConstantTable(method_address, value);
     if (!materialize) {
       load_constant->MarkEmittedAtUseSite();
     }
@@ -204,9 +213,9 @@
     if (invoke_static_or_direct != nullptr &&
         invoke_static_or_direct->HasPcRelativeDexCache() &&
         !IsCallFreeIntrinsic<IntrinsicLocationsBuilderX86>(invoke, codegen_)) {
-      InitializePCRelativeBasePointer();
-      // Add the extra parameter base_.
-      invoke_static_or_direct->AddSpecialInput(base_);
+      HX86ComputeBaseMethodAddress* method_address = GetPCRelativeBasePointer(invoke);
+      // Add the extra parameter.
+      invoke_static_or_direct->AddSpecialInput(method_address);
       base_added = true;
     }
 
@@ -231,8 +240,8 @@
         if (!base_added) {
           DCHECK(invoke_static_or_direct != nullptr);
           DCHECK(!invoke_static_or_direct->HasCurrentMethodInput());
-          InitializePCRelativeBasePointer();
-          invoke_static_or_direct->AddSpecialInput(base_);
+          HX86ComputeBaseMethodAddress* method_address = GetPCRelativeBasePointer(invoke);
+          invoke_static_or_direct->AddSpecialInput(method_address);
         }
         break;
       default:
@@ -243,16 +252,12 @@
   CodeGeneratorX86* codegen_;
 
   // The generated HX86ComputeBaseMethodAddress in the entry block needed as an
-  // input to the HX86LoadFromConstantTable instructions.
+  // input to the HX86LoadFromConstantTable instructions. Only set for
+  // graphs with reducible loops.
   HX86ComputeBaseMethodAddress* base_;
 };
 
 void PcRelativeFixups::Run() {
-  if (graph_->HasIrreducibleLoops()) {
-    // Do not run this optimization, as irreducible loops do not work with an instruction
-    // that can be live-in at the irreducible loop header.
-    return;
-  }
   PCRelativeHandlerVisitor visitor(graph_, codegen_);
   visitor.VisitInsertionOrder();
   visitor.MoveBaseIfNeeded();
diff --git a/compiler/optimizing/stack_map_stream.cc b/compiler/optimizing/stack_map_stream.cc
index 6087e36..a9a1e6f 100644
--- a/compiler/optimizing/stack_map_stream.cc
+++ b/compiler/optimizing/stack_map_stream.cc
@@ -31,7 +31,7 @@
   DCHECK_EQ(0u, current_entry_.dex_pc) << "EndStackMapEntry not called after BeginStackMapEntry";
   DCHECK_NE(dex_pc, static_cast<uint32_t>(-1)) << "invalid dex_pc";
   current_entry_.dex_pc = dex_pc;
-  current_entry_.native_pc_offset = native_pc_offset;
+  current_entry_.native_pc_code_offset = CodeOffset::FromOffset(native_pc_offset, instruction_set_);
   current_entry_.register_mask = register_mask;
   current_entry_.sp_mask = sp_mask;
   current_entry_.num_dex_registers = num_dex_registers;
@@ -144,10 +144,10 @@
   current_inline_info_ = InlineInfoEntry();
 }
 
-uint32_t StackMapStream::ComputeMaxNativePcOffset() const {
-  uint32_t max_native_pc_offset = 0u;
+CodeOffset StackMapStream::ComputeMaxNativePcCodeOffset() const {
+  CodeOffset max_native_pc_offset;
   for (const StackMapEntry& entry : stack_maps_) {
-    max_native_pc_offset = std::max(max_native_pc_offset, entry.native_pc_offset);
+    max_native_pc_offset = std::max(max_native_pc_offset, entry.native_pc_code_offset);
   }
   return max_native_pc_offset;
 }
@@ -157,8 +157,9 @@
   dex_register_maps_size_ = ComputeDexRegisterMapsSize();
   ComputeInlineInfoEncoding();  // needs dex_register_maps_size_.
   inline_info_size_ = inline_infos_.size() * inline_info_encoding_.GetEntrySize();
-  uint32_t max_native_pc_offset = ComputeMaxNativePcOffset();
-  size_t stack_map_size = stack_map_encoding_.SetFromSizes(max_native_pc_offset,
+  CodeOffset max_native_pc_offset = ComputeMaxNativePcCodeOffset();
+  // The stack map contains compressed native offsets.
+  size_t stack_map_size = stack_map_encoding_.SetFromSizes(max_native_pc_offset.CompressedValue(),
                                                            dex_pc_max_,
                                                            dex_register_maps_size_,
                                                            inline_info_size_,
@@ -319,7 +320,7 @@
     StackMapEntry entry = stack_maps_[i];
 
     stack_map.SetDexPc(stack_map_encoding_, entry.dex_pc);
-    stack_map.SetNativePcOffset(stack_map_encoding_, entry.native_pc_offset);
+    stack_map.SetNativePcCodeOffset(stack_map_encoding_, entry.native_pc_code_offset);
     stack_map.SetRegisterMask(stack_map_encoding_, entry.register_mask);
     size_t number_of_stack_mask_bits = stack_map.GetNumberOfStackMaskBits(stack_map_encoding_);
     if (entry.sp_mask != nullptr) {
@@ -546,7 +547,8 @@
     StackMapEntry entry = stack_maps_[s];
 
     // Check main stack map fields.
-    DCHECK_EQ(stack_map.GetNativePcOffset(stack_map_encoding), entry.native_pc_offset);
+    DCHECK_EQ(stack_map.GetNativePcOffset(stack_map_encoding, instruction_set_),
+              entry.native_pc_code_offset.Uint32Value(instruction_set_));
     DCHECK_EQ(stack_map.GetDexPc(stack_map_encoding), entry.dex_pc);
     DCHECK_EQ(stack_map.GetRegisterMask(stack_map_encoding), entry.register_mask);
     size_t num_stack_mask_bits = stack_map.GetNumberOfStackMaskBits(stack_map_encoding);
diff --git a/compiler/optimizing/stack_map_stream.h b/compiler/optimizing/stack_map_stream.h
index d6f42b3..8fec472 100644
--- a/compiler/optimizing/stack_map_stream.h
+++ b/compiler/optimizing/stack_map_stream.h
@@ -59,8 +59,10 @@
  */
 class StackMapStream : public ValueObject {
  public:
-  explicit StackMapStream(ArenaAllocator* allocator)
+  explicit StackMapStream(ArenaAllocator* allocator,
+                          InstructionSet instruction_set)
       : allocator_(allocator),
+        instruction_set_(instruction_set),
         stack_maps_(allocator->Adapter(kArenaAllocStackMapStream)),
         location_catalog_entries_(allocator->Adapter(kArenaAllocStackMapStream)),
         location_catalog_entries_indices_(allocator->Adapter(kArenaAllocStackMapStream)),
@@ -95,7 +97,7 @@
   // See runtime/stack_map.h to know what these fields contain.
   struct StackMapEntry {
     uint32_t dex_pc;
-    uint32_t native_pc_offset;
+    CodeOffset native_pc_code_offset;
     uint32_t register_mask;
     BitVector* sp_mask;
     uint32_t num_dex_registers;
@@ -141,11 +143,9 @@
   }
 
   void SetStackMapNativePcOffset(size_t i, uint32_t native_pc_offset) {
-    stack_maps_[i].native_pc_offset = native_pc_offset;
+    stack_maps_[i].native_pc_code_offset = CodeOffset::FromOffset(native_pc_offset, instruction_set_);
   }
 
-  uint32_t ComputeMaxNativePcOffset() const;
-
   // Prepares the stream to fill in a memory region. Must be called before FillIn.
   // Returns the size (in bytes) needed to store this stream.
   size_t PrepareForFillIn();
@@ -158,6 +158,8 @@
   size_t ComputeDexRegisterMapsSize() const;
   void ComputeInlineInfoEncoding();
 
+  CodeOffset ComputeMaxNativePcCodeOffset() const;
+
   // Returns the index of an entry with the same dex register map as the current_entry,
   // or kNoSameDexMapFound if no such entry exists.
   size_t FindEntryWithTheSameDexMap();
@@ -175,6 +177,7 @@
   void CheckCodeInfo(MemoryRegion region) const;
 
   ArenaAllocator* allocator_;
+  const InstructionSet instruction_set_;
   ArenaVector<StackMapEntry> stack_maps_;
 
   // A catalog of unique [location_kind, register_value] pairs (per method).
diff --git a/compiler/optimizing/stack_map_test.cc b/compiler/optimizing/stack_map_test.cc
index 22810ea..f68695b 100644
--- a/compiler/optimizing/stack_map_test.cc
+++ b/compiler/optimizing/stack_map_test.cc
@@ -47,7 +47,7 @@
 TEST(StackMapTest, Test1) {
   ArenaPool pool;
   ArenaAllocator arena(&pool);
-  StackMapStream stream(&arena);
+  StackMapStream stream(&arena, kRuntimeISA);
 
   ArenaBitVector sp_mask(&arena, 0, false);
   size_t number_of_dex_registers = 2;
@@ -78,7 +78,7 @@
   ASSERT_TRUE(stack_map.Equals(code_info.GetStackMapForDexPc(0, encoding)));
   ASSERT_TRUE(stack_map.Equals(code_info.GetStackMapForNativePcOffset(64, encoding)));
   ASSERT_EQ(0u, stack_map.GetDexPc(encoding.stack_map_encoding));
-  ASSERT_EQ(64u, stack_map.GetNativePcOffset(encoding.stack_map_encoding));
+  ASSERT_EQ(64u, stack_map.GetNativePcOffset(encoding.stack_map_encoding, kRuntimeISA));
   ASSERT_EQ(0x3u, stack_map.GetRegisterMask(encoding.stack_map_encoding));
 
   ASSERT_TRUE(CheckStackMask(stack_map, encoding.stack_map_encoding, sp_mask));
@@ -128,7 +128,7 @@
 TEST(StackMapTest, Test2) {
   ArenaPool pool;
   ArenaAllocator arena(&pool);
-  StackMapStream stream(&arena);
+  StackMapStream stream(&arena, kRuntimeISA);
   ArtMethod art_method;
 
   ArenaBitVector sp_mask1(&arena, 0, true);
@@ -193,7 +193,7 @@
     ASSERT_TRUE(stack_map.Equals(code_info.GetStackMapForDexPc(0, encoding)));
     ASSERT_TRUE(stack_map.Equals(code_info.GetStackMapForNativePcOffset(64, encoding)));
     ASSERT_EQ(0u, stack_map.GetDexPc(encoding.stack_map_encoding));
-    ASSERT_EQ(64u, stack_map.GetNativePcOffset(encoding.stack_map_encoding));
+    ASSERT_EQ(64u, stack_map.GetNativePcOffset(encoding.stack_map_encoding, kRuntimeISA));
     ASSERT_EQ(0x3u, stack_map.GetRegisterMask(encoding.stack_map_encoding));
 
     ASSERT_TRUE(CheckStackMask(stack_map, encoding.stack_map_encoding, sp_mask1));
@@ -252,7 +252,7 @@
     ASSERT_TRUE(stack_map.Equals(code_info.GetStackMapForDexPc(1u, encoding)));
     ASSERT_TRUE(stack_map.Equals(code_info.GetStackMapForNativePcOffset(128u, encoding)));
     ASSERT_EQ(1u, stack_map.GetDexPc(encoding.stack_map_encoding));
-    ASSERT_EQ(128u, stack_map.GetNativePcOffset(encoding.stack_map_encoding));
+    ASSERT_EQ(128u, stack_map.GetNativePcOffset(encoding.stack_map_encoding, kRuntimeISA));
     ASSERT_EQ(0xFFu, stack_map.GetRegisterMask(encoding.stack_map_encoding));
 
     ASSERT_TRUE(CheckStackMask(stack_map, encoding.stack_map_encoding, sp_mask2));
@@ -306,7 +306,7 @@
     ASSERT_TRUE(stack_map.Equals(code_info.GetStackMapForDexPc(2u, encoding)));
     ASSERT_TRUE(stack_map.Equals(code_info.GetStackMapForNativePcOffset(192u, encoding)));
     ASSERT_EQ(2u, stack_map.GetDexPc(encoding.stack_map_encoding));
-    ASSERT_EQ(192u, stack_map.GetNativePcOffset(encoding.stack_map_encoding));
+    ASSERT_EQ(192u, stack_map.GetNativePcOffset(encoding.stack_map_encoding, kRuntimeISA));
     ASSERT_EQ(0xABu, stack_map.GetRegisterMask(encoding.stack_map_encoding));
 
     ASSERT_TRUE(CheckStackMask(stack_map, encoding.stack_map_encoding, sp_mask3));
@@ -360,7 +360,7 @@
     ASSERT_TRUE(stack_map.Equals(code_info.GetStackMapForDexPc(3u, encoding)));
     ASSERT_TRUE(stack_map.Equals(code_info.GetStackMapForNativePcOffset(256u, encoding)));
     ASSERT_EQ(3u, stack_map.GetDexPc(encoding.stack_map_encoding));
-    ASSERT_EQ(256u, stack_map.GetNativePcOffset(encoding.stack_map_encoding));
+    ASSERT_EQ(256u, stack_map.GetNativePcOffset(encoding.stack_map_encoding, kRuntimeISA));
     ASSERT_EQ(0xCDu, stack_map.GetRegisterMask(encoding.stack_map_encoding));
 
     ASSERT_TRUE(CheckStackMask(stack_map, encoding.stack_map_encoding, sp_mask4));
@@ -412,7 +412,7 @@
 TEST(StackMapTest, TestNonLiveDexRegisters) {
   ArenaPool pool;
   ArenaAllocator arena(&pool);
-  StackMapStream stream(&arena);
+  StackMapStream stream(&arena, kRuntimeISA);
 
   ArenaBitVector sp_mask(&arena, 0, false);
   uint32_t number_of_dex_registers = 2;
@@ -442,7 +442,7 @@
   ASSERT_TRUE(stack_map.Equals(code_info.GetStackMapForDexPc(0, encoding)));
   ASSERT_TRUE(stack_map.Equals(code_info.GetStackMapForNativePcOffset(64, encoding)));
   ASSERT_EQ(0u, stack_map.GetDexPc(encoding.stack_map_encoding));
-  ASSERT_EQ(64u, stack_map.GetNativePcOffset(encoding.stack_map_encoding));
+  ASSERT_EQ(64u, stack_map.GetNativePcOffset(encoding.stack_map_encoding, kRuntimeISA));
   ASSERT_EQ(0x3u, stack_map.GetRegisterMask(encoding.stack_map_encoding));
 
   ASSERT_TRUE(stack_map.HasDexRegisterMap(encoding.stack_map_encoding));
@@ -491,7 +491,7 @@
 TEST(StackMapTest, DexRegisterMapOffsetOverflow) {
   ArenaPool pool;
   ArenaAllocator arena(&pool);
-  StackMapStream stream(&arena);
+  StackMapStream stream(&arena, kRuntimeISA);
 
   ArenaBitVector sp_mask(&arena, 0, false);
   uint32_t number_of_dex_registers = 1024;
@@ -554,7 +554,7 @@
 TEST(StackMapTest, TestShareDexRegisterMap) {
   ArenaPool pool;
   ArenaAllocator arena(&pool);
-  StackMapStream stream(&arena);
+  StackMapStream stream(&arena, kRuntimeISA);
 
   ArenaBitVector sp_mask(&arena, 0, false);
   uint32_t number_of_dex_registers = 2;
@@ -612,7 +612,7 @@
 TEST(StackMapTest, TestNoDexRegisterMap) {
   ArenaPool pool;
   ArenaAllocator arena(&pool);
-  StackMapStream stream(&arena);
+  StackMapStream stream(&arena, kRuntimeISA);
 
   ArenaBitVector sp_mask(&arena, 0, false);
   uint32_t number_of_dex_registers = 0;
@@ -620,7 +620,7 @@
   stream.EndStackMapEntry();
 
   number_of_dex_registers = 1;
-  stream.BeginStackMapEntry(1, 67, 0x4, &sp_mask, number_of_dex_registers, 0);
+  stream.BeginStackMapEntry(1, 68, 0x4, &sp_mask, number_of_dex_registers, 0);
   stream.EndStackMapEntry();
 
   size_t size = stream.PrepareForFillIn();
@@ -641,7 +641,7 @@
   ASSERT_TRUE(stack_map.Equals(code_info.GetStackMapForDexPc(0, encoding)));
   ASSERT_TRUE(stack_map.Equals(code_info.GetStackMapForNativePcOffset(64, encoding)));
   ASSERT_EQ(0u, stack_map.GetDexPc(encoding.stack_map_encoding));
-  ASSERT_EQ(64u, stack_map.GetNativePcOffset(encoding.stack_map_encoding));
+  ASSERT_EQ(64u, stack_map.GetNativePcOffset(encoding.stack_map_encoding, kRuntimeISA));
   ASSERT_EQ(0x3u, stack_map.GetRegisterMask(encoding.stack_map_encoding));
 
   ASSERT_FALSE(stack_map.HasDexRegisterMap(encoding.stack_map_encoding));
@@ -649,9 +649,9 @@
 
   stack_map = code_info.GetStackMapAt(1, encoding);
   ASSERT_TRUE(stack_map.Equals(code_info.GetStackMapForDexPc(1, encoding)));
-  ASSERT_TRUE(stack_map.Equals(code_info.GetStackMapForNativePcOffset(67, encoding)));
+  ASSERT_TRUE(stack_map.Equals(code_info.GetStackMapForNativePcOffset(68, encoding)));
   ASSERT_EQ(1u, stack_map.GetDexPc(encoding.stack_map_encoding));
-  ASSERT_EQ(67u, stack_map.GetNativePcOffset(encoding.stack_map_encoding));
+  ASSERT_EQ(68u, stack_map.GetNativePcOffset(encoding.stack_map_encoding, kRuntimeISA));
   ASSERT_EQ(0x4u, stack_map.GetRegisterMask(encoding.stack_map_encoding));
 
   ASSERT_FALSE(stack_map.HasDexRegisterMap(encoding.stack_map_encoding));
@@ -661,7 +661,7 @@
 TEST(StackMapTest, InlineTest) {
   ArenaPool pool;
   ArenaAllocator arena(&pool);
-  StackMapStream stream(&arena);
+  StackMapStream stream(&arena, kRuntimeISA);
   ArtMethod art_method;
 
   ArenaBitVector sp_mask1(&arena, 0, true);
@@ -823,4 +823,20 @@
   }
 }
 
+TEST(StackMapTest, CodeOffsetTest) {
+  // Test minimum alignments, encoding, and decoding.
+  CodeOffset offset_thumb2 = CodeOffset::FromOffset(kThumb2InstructionAlignment, kThumb2);
+  CodeOffset offset_arm64 = CodeOffset::FromOffset(kArm64InstructionAlignment, kArm64);
+  CodeOffset offset_x86 = CodeOffset::FromOffset(kX86InstructionAlignment, kX86);
+  CodeOffset offset_x86_64 = CodeOffset::FromOffset(kX86_64InstructionAlignment, kX86_64);
+  CodeOffset offset_mips = CodeOffset::FromOffset(kMipsInstructionAlignment, kMips);
+  CodeOffset offset_mips64 = CodeOffset::FromOffset(kMips64InstructionAlignment, kMips64);
+  EXPECT_EQ(offset_thumb2.Uint32Value(kThumb2), kThumb2InstructionAlignment);
+  EXPECT_EQ(offset_arm64.Uint32Value(kArm64), kArm64InstructionAlignment);
+  EXPECT_EQ(offset_x86.Uint32Value(kX86), kX86InstructionAlignment);
+  EXPECT_EQ(offset_x86_64.Uint32Value(kX86_64), kX86_64InstructionAlignment);
+  EXPECT_EQ(offset_mips.Uint32Value(kMips), kMipsInstructionAlignment);
+  EXPECT_EQ(offset_mips64.Uint32Value(kMips64), kMips64InstructionAlignment);
+}
+
 }  // namespace art
diff --git a/compiler/utils/mips64/assembler_mips64_test.cc b/compiler/utils/mips64/assembler_mips64_test.cc
index f2cbebb..74b8f06 100644
--- a/compiler/utils/mips64/assembler_mips64_test.cc
+++ b/compiler/utils/mips64/assembler_mips64_test.cc
@@ -283,6 +283,38 @@
 // FP Operations //
 ///////////////////
 
+TEST_F(AssemblerMIPS64Test, AddS) {
+  DriverStr(RepeatFFF(&mips64::Mips64Assembler::AddS, "add.s ${reg1}, ${reg2}, ${reg3}"), "add.s");
+}
+
+TEST_F(AssemblerMIPS64Test, AddD) {
+  DriverStr(RepeatFFF(&mips64::Mips64Assembler::AddD, "add.d ${reg1}, ${reg2}, ${reg3}"), "add.d");
+}
+
+TEST_F(AssemblerMIPS64Test, SubS) {
+  DriverStr(RepeatFFF(&mips64::Mips64Assembler::SubS, "sub.s ${reg1}, ${reg2}, ${reg3}"), "sub.s");
+}
+
+TEST_F(AssemblerMIPS64Test, SubD) {
+  DriverStr(RepeatFFF(&mips64::Mips64Assembler::SubD, "sub.d ${reg1}, ${reg2}, ${reg3}"), "sub.d");
+}
+
+TEST_F(AssemblerMIPS64Test, MulS) {
+  DriverStr(RepeatFFF(&mips64::Mips64Assembler::MulS, "mul.s ${reg1}, ${reg2}, ${reg3}"), "mul.s");
+}
+
+TEST_F(AssemblerMIPS64Test, MulD) {
+  DriverStr(RepeatFFF(&mips64::Mips64Assembler::MulD, "mul.d ${reg1}, ${reg2}, ${reg3}"), "mul.d");
+}
+
+TEST_F(AssemblerMIPS64Test, DivS) {
+  DriverStr(RepeatFFF(&mips64::Mips64Assembler::DivS, "div.s ${reg1}, ${reg2}, ${reg3}"), "div.s");
+}
+
+TEST_F(AssemblerMIPS64Test, DivD) {
+  DriverStr(RepeatFFF(&mips64::Mips64Assembler::DivD, "div.d ${reg1}, ${reg2}, ${reg3}"), "div.d");
+}
+
 TEST_F(AssemblerMIPS64Test, SqrtS) {
   DriverStr(RepeatFF(&mips64::Mips64Assembler::SqrtS, "sqrt.s ${reg1}, ${reg2}"), "sqrt.s");
 }
@@ -567,6 +599,26 @@
   DriverStr(RepeatRF(&mips64::Mips64Assembler::Dmtc1, "dmtc1 ${reg1}, ${reg2}"), "Dmtc1");
 }
 
+TEST_F(AssemblerMIPS64Test, Lwc1) {
+  DriverStr(RepeatFRIb(&mips64::Mips64Assembler::Lwc1, -16, "lwc1 ${reg1}, {imm}(${reg2})"),
+            "lwc1");
+}
+
+TEST_F(AssemblerMIPS64Test, Ldc1) {
+  DriverStr(RepeatFRIb(&mips64::Mips64Assembler::Ldc1, -16, "ldc1 ${reg1}, {imm}(${reg2})"),
+            "ldc1");
+}
+
+TEST_F(AssemblerMIPS64Test, Swc1) {
+  DriverStr(RepeatFRIb(&mips64::Mips64Assembler::Swc1, -16, "swc1 ${reg1}, {imm}(${reg2})"),
+            "swc1");
+}
+
+TEST_F(AssemblerMIPS64Test, Sdc1) {
+  DriverStr(RepeatFRIb(&mips64::Mips64Assembler::Sdc1, -16, "sdc1 ${reg1}, {imm}(${reg2})"),
+            "sdc1");
+}
+
 ////////////////
 // CALL / JMP //
 ////////////////
@@ -850,6 +902,16 @@
   DriverStr(RepeatRIb(&mips64::Mips64Assembler::Ldpc, 18, code), "Ldpc");
 }
 
+TEST_F(AssemblerMIPS64Test, Auipc) {
+  DriverStr(RepeatRIb(&mips64::Mips64Assembler::Auipc, 16, "auipc ${reg}, {imm}"), "Auipc");
+}
+
+TEST_F(AssemblerMIPS64Test, Addiupc) {
+  // The comment from the Lwpc() test applies to this Addiupc() test as well.
+  const char* code = ".set imm, {imm}\naddiupc ${reg}, (imm - ((imm & 0x40000) << 1)) << 2";
+  DriverStr(RepeatRIb(&mips64::Mips64Assembler::Addiupc, 19, code), "Addiupc");
+}
+
 TEST_F(AssemblerMIPS64Test, LoadFarthestNearLabelAddress) {
   mips64::Mips64Label label;
   __ LoadLabelAddress(mips64::V0, &label);
@@ -1079,6 +1141,188 @@
   EXPECT_EQ(__ GetLabelLocation(literal->GetLabel()), (5 + kAdduCount) * 4);
 }
 
+TEST_F(AssemblerMIPS64Test, Addu) {
+  DriverStr(RepeatRRR(&mips64::Mips64Assembler::Addu, "addu ${reg1}, ${reg2}, ${reg3}"), "addu");
+}
+
+TEST_F(AssemblerMIPS64Test, Addiu) {
+  DriverStr(RepeatRRIb(&mips64::Mips64Assembler::Addiu, -16, "addiu ${reg1}, ${reg2}, {imm}"),
+            "addiu");
+}
+
+TEST_F(AssemblerMIPS64Test, Daddu) {
+  DriverStr(RepeatRRR(&mips64::Mips64Assembler::Daddu, "daddu ${reg1}, ${reg2}, ${reg3}"), "daddu");
+}
+
+TEST_F(AssemblerMIPS64Test, Daddiu) {
+  DriverStr(RepeatRRIb(&mips64::Mips64Assembler::Daddiu, -16, "daddiu ${reg1}, ${reg2}, {imm}"),
+            "daddiu");
+}
+
+TEST_F(AssemblerMIPS64Test, Subu) {
+  DriverStr(RepeatRRR(&mips64::Mips64Assembler::Subu, "subu ${reg1}, ${reg2}, ${reg3}"), "subu");
+}
+
+TEST_F(AssemblerMIPS64Test, Dsubu) {
+  DriverStr(RepeatRRR(&mips64::Mips64Assembler::Dsubu, "dsubu ${reg1}, ${reg2}, ${reg3}"), "dsubu");
+}
+
+TEST_F(AssemblerMIPS64Test, MulR6) {
+  DriverStr(RepeatRRR(&mips64::Mips64Assembler::MulR6, "mul ${reg1}, ${reg2}, ${reg3}"), "mulR6");
+}
+
+TEST_F(AssemblerMIPS64Test, DivR6) {
+  DriverStr(RepeatRRR(&mips64::Mips64Assembler::DivR6, "div ${reg1}, ${reg2}, ${reg3}"), "divR6");
+}
+
+TEST_F(AssemblerMIPS64Test, ModR6) {
+  DriverStr(RepeatRRR(&mips64::Mips64Assembler::ModR6, "mod ${reg1}, ${reg2}, ${reg3}"), "modR6");
+}
+
+TEST_F(AssemblerMIPS64Test, DivuR6) {
+  DriverStr(RepeatRRR(&mips64::Mips64Assembler::DivuR6, "divu ${reg1}, ${reg2}, ${reg3}"),
+            "divuR6");
+}
+
+TEST_F(AssemblerMIPS64Test, ModuR6) {
+  DriverStr(RepeatRRR(&mips64::Mips64Assembler::ModuR6, "modu ${reg1}, ${reg2}, ${reg3}"),
+            "moduR6");
+}
+
+TEST_F(AssemblerMIPS64Test, Dmul) {
+  DriverStr(RepeatRRR(&mips64::Mips64Assembler::Dmul, "dmul ${reg1}, ${reg2}, ${reg3}"), "dmul");
+}
+
+TEST_F(AssemblerMIPS64Test, Ddiv) {
+  DriverStr(RepeatRRR(&mips64::Mips64Assembler::Ddiv, "ddiv ${reg1}, ${reg2}, ${reg3}"), "ddiv");
+}
+
+TEST_F(AssemblerMIPS64Test, Dmod) {
+  DriverStr(RepeatRRR(&mips64::Mips64Assembler::Dmod, "dmod ${reg1}, ${reg2}, ${reg3}"), "dmod");
+}
+
+TEST_F(AssemblerMIPS64Test, Ddivu) {
+  DriverStr(RepeatRRR(&mips64::Mips64Assembler::Ddivu, "ddivu ${reg1}, ${reg2}, ${reg3}"), "ddivu");
+}
+
+TEST_F(AssemblerMIPS64Test, Dmodu) {
+  DriverStr(RepeatRRR(&mips64::Mips64Assembler::Dmodu, "dmodu ${reg1}, ${reg2}, ${reg3}"), "dmodu");
+}
+
+TEST_F(AssemblerMIPS64Test, And) {
+  DriverStr(RepeatRRR(&mips64::Mips64Assembler::And, "and ${reg1}, ${reg2}, ${reg3}"), "and");
+}
+
+TEST_F(AssemblerMIPS64Test, Andi) {
+  DriverStr(RepeatRRIb(&mips64::Mips64Assembler::Andi, 16, "andi ${reg1}, ${reg2}, {imm}"), "andi");
+}
+
+TEST_F(AssemblerMIPS64Test, Or) {
+  DriverStr(RepeatRRR(&mips64::Mips64Assembler::Or, "or ${reg1}, ${reg2}, ${reg3}"), "or");
+}
+
+TEST_F(AssemblerMIPS64Test, Ori) {
+  DriverStr(RepeatRRIb(&mips64::Mips64Assembler::Ori, 16, "ori ${reg1}, ${reg2}, {imm}"), "ori");
+}
+
+TEST_F(AssemblerMIPS64Test, Xor) {
+  DriverStr(RepeatRRR(&mips64::Mips64Assembler::Xor, "xor ${reg1}, ${reg2}, ${reg3}"), "xor");
+}
+
+TEST_F(AssemblerMIPS64Test, Xori) {
+  DriverStr(RepeatRRIb(&mips64::Mips64Assembler::Xori, 16, "xori ${reg1}, ${reg2}, {imm}"), "xori");
+}
+
+TEST_F(AssemblerMIPS64Test, Nor) {
+  DriverStr(RepeatRRR(&mips64::Mips64Assembler::Nor, "nor ${reg1}, ${reg2}, ${reg3}"), "nor");
+}
+
+TEST_F(AssemblerMIPS64Test, Lb) {
+  DriverStr(RepeatRRIb(&mips64::Mips64Assembler::Lb, -16, "lb ${reg1}, {imm}(${reg2})"), "lb");
+}
+
+TEST_F(AssemblerMIPS64Test, Lh) {
+  DriverStr(RepeatRRIb(&mips64::Mips64Assembler::Lh, -16, "lh ${reg1}, {imm}(${reg2})"), "lh");
+}
+
+TEST_F(AssemblerMIPS64Test, Lw) {
+  DriverStr(RepeatRRIb(&mips64::Mips64Assembler::Lw, -16, "lw ${reg1}, {imm}(${reg2})"), "lw");
+}
+
+TEST_F(AssemblerMIPS64Test, Ld) {
+  DriverStr(RepeatRRIb(&mips64::Mips64Assembler::Ld, -16, "ld ${reg1}, {imm}(${reg2})"), "ld");
+}
+
+TEST_F(AssemblerMIPS64Test, Lbu) {
+  DriverStr(RepeatRRIb(&mips64::Mips64Assembler::Lbu, -16, "lbu ${reg1}, {imm}(${reg2})"), "lbu");
+}
+
+TEST_F(AssemblerMIPS64Test, Lhu) {
+  DriverStr(RepeatRRIb(&mips64::Mips64Assembler::Lhu, -16, "lhu ${reg1}, {imm}(${reg2})"), "lhu");
+}
+
+TEST_F(AssemblerMIPS64Test, Lwu) {
+  DriverStr(RepeatRRIb(&mips64::Mips64Assembler::Lwu, -16, "lwu ${reg1}, {imm}(${reg2})"), "lwu");
+}
+
+TEST_F(AssemblerMIPS64Test, Lui) {
+  DriverStr(RepeatRIb(&mips64::Mips64Assembler::Lui, 16, "lui ${reg}, {imm}"), "lui");
+}
+
+TEST_F(AssemblerMIPS64Test, Dahi) {
+  DriverStr(RepeatRIb(&mips64::Mips64Assembler::Dahi, 16, "dahi ${reg}, ${reg}, {imm}"), "dahi");
+}
+
+TEST_F(AssemblerMIPS64Test, Dati) {
+  DriverStr(RepeatRIb(&mips64::Mips64Assembler::Dati, 16, "dati ${reg}, ${reg}, {imm}"), "dati");
+}
+
+TEST_F(AssemblerMIPS64Test, Sb) {
+  DriverStr(RepeatRRIb(&mips64::Mips64Assembler::Sb, -16, "sb ${reg1}, {imm}(${reg2})"), "sb");
+}
+
+TEST_F(AssemblerMIPS64Test, Sh) {
+  DriverStr(RepeatRRIb(&mips64::Mips64Assembler::Sh, -16, "sh ${reg1}, {imm}(${reg2})"), "sh");
+}
+
+TEST_F(AssemblerMIPS64Test, Sw) {
+  DriverStr(RepeatRRIb(&mips64::Mips64Assembler::Sw, -16, "sw ${reg1}, {imm}(${reg2})"), "sw");
+}
+
+TEST_F(AssemblerMIPS64Test, Sd) {
+  DriverStr(RepeatRRIb(&mips64::Mips64Assembler::Sd, -16, "sd ${reg1}, {imm}(${reg2})"), "sd");
+}
+
+TEST_F(AssemblerMIPS64Test, Slt) {
+  DriverStr(RepeatRRR(&mips64::Mips64Assembler::Slt, "slt ${reg1}, ${reg2}, ${reg3}"), "slt");
+}
+
+TEST_F(AssemblerMIPS64Test, Sltu) {
+  DriverStr(RepeatRRR(&mips64::Mips64Assembler::Sltu, "sltu ${reg1}, ${reg2}, ${reg3}"), "sltu");
+}
+
+TEST_F(AssemblerMIPS64Test, Slti) {
+  DriverStr(RepeatRRIb(&mips64::Mips64Assembler::Slti, -16, "slti ${reg1}, ${reg2}, {imm}"),
+            "slti");
+}
+
+TEST_F(AssemblerMIPS64Test, Sltiu) {
+  DriverStr(RepeatRRIb(&mips64::Mips64Assembler::Sltiu, -16, "sltiu ${reg1}, ${reg2}, {imm}"),
+            "sltiu");
+}
+
+TEST_F(AssemblerMIPS64Test, Move) {
+  DriverStr(RepeatRR(&mips64::Mips64Assembler::Move, "or ${reg1}, ${reg2}, $zero"), "move");
+}
+
+TEST_F(AssemblerMIPS64Test, Clear) {
+  DriverStr(RepeatR(&mips64::Mips64Assembler::Clear, "or ${reg}, $zero, $zero"), "clear");
+}
+
+TEST_F(AssemblerMIPS64Test, Not) {
+  DriverStr(RepeatRR(&mips64::Mips64Assembler::Not, "nor ${reg1}, ${reg2}, $zero"), "not");
+}
+
 TEST_F(AssemblerMIPS64Test, Bitswap) {
   DriverStr(RepeatRR(&mips64::Mips64Assembler::Bitswap, "bitswap ${reg1}, ${reg2}"), "bitswap");
 }
@@ -1230,6 +1474,18 @@
             "dsra32");
 }
 
+TEST_F(AssemblerMIPS64Test, Dsllv) {
+  DriverStr(RepeatRRR(&mips64::Mips64Assembler::Dsllv, "dsllv ${reg1}, ${reg2}, ${reg3}"), "dsllv");
+}
+
+TEST_F(AssemblerMIPS64Test, Dsrlv) {
+  DriverStr(RepeatRRR(&mips64::Mips64Assembler::Dsrlv, "dsrlv ${reg1}, ${reg2}, ${reg3}"), "dsrlv");
+}
+
+TEST_F(AssemblerMIPS64Test, Dsrav) {
+  DriverStr(RepeatRRR(&mips64::Mips64Assembler::Dsrav, "dsrav ${reg1}, ${reg2}, ${reg3}"), "dsrav");
+}
+
 TEST_F(AssemblerMIPS64Test, Sc) {
   DriverStr(RepeatRRIb(&mips64::Mips64Assembler::Sc, -9, "sc ${reg1}, {imm}(${reg2})"), "sc");
 }
diff --git a/compiler/verifier_deps_test.cc b/compiler/verifier_deps_test.cc
index 4f06a91..5fc9972 100644
--- a/compiler/verifier_deps_test.cc
+++ b/compiler/verifier_deps_test.cc
@@ -1414,7 +1414,14 @@
       ASSERT_FALSE(decoded_deps.ValidateDependencies(new_class_loader, soa.Self()));
     }
 
-    {
+    // The two tests below make sure that fiddling with the method kind
+    // (static, virtual, interface) is detected by `ValidateDependencies`.
+
+    // An interface method lookup can succeed with a virtual method lookup on the same class.
+    // That's OK, as we only want to make sure there is a method being defined with the right
+    // flags. Therefore, polluting the interface methods with virtual methods does not have
+    // to fail verification.
+    if (resolution_kind != kVirtualMethodResolution) {
       VerifierDeps decoded_deps(dex_files_, ArrayRef<const uint8_t>(buffer));
       VerifierDeps::DexFileDeps* deps = decoded_deps.GetDexFileDeps(*primary_dex_file_);
       bool found = false;
@@ -1433,7 +1440,8 @@
       ASSERT_FALSE(decoded_deps.ValidateDependencies(new_class_loader, soa.Self()));
     }
 
-    {
+    // See comment above that applies the same way.
+    if (resolution_kind != kInterfaceMethodResolution) {
       VerifierDeps decoded_deps(dex_files_, ArrayRef<const uint8_t>(buffer));
       VerifierDeps::DexFileDeps* deps = decoded_deps.GetDexFileDeps(*primary_dex_file_);
       bool found = false;
diff --git a/dex2oat/dex2oat.cc b/dex2oat/dex2oat.cc
index 3fbdb89..e8a92c1 100644
--- a/dex2oat/dex2oat.cc
+++ b/dex2oat/dex2oat.cc
@@ -64,7 +64,7 @@
 #include "gc/space/space-inl.h"
 #include "image_writer.h"
 #include "interpreter/unstarted_runtime.h"
-#include "jit/offline_profiling_info.h"
+#include "jit/profile_compilation_info.h"
 #include "leb128.h"
 #include "linker/buffered_output_stream.h"
 #include "linker/file_output_stream.h"
diff --git a/dex2oat/dex2oat_test.cc b/dex2oat/dex2oat_test.cc
index cdb3b9f..e86e560 100644
--- a/dex2oat/dex2oat_test.cc
+++ b/dex2oat/dex2oat_test.cc
@@ -30,7 +30,7 @@
 #include "base/macros.h"
 #include "dex_file-inl.h"
 #include "dex2oat_environment_test.h"
-#include "jit/offline_profiling_info.h"
+#include "jit/profile_compilation_info.h"
 #include "oat.h"
 #include "oat_file.h"
 #include "utils.h"
diff --git a/dexlayout/dex_ir.h b/dexlayout/dex_ir.h
index a2d1190..e2ee940 100644
--- a/dexlayout/dex_ir.h
+++ b/dexlayout/dex_ir.h
@@ -741,7 +741,7 @@
   uint32_t GetAccessFlags() const { return access_flags_; }
   const TypeId* Superclass() const { return superclass_; }
   const TypeIdVector* Interfaces()
-      { return interfaces_ == nullptr ? nullptr: interfaces_->GetTypeList(); }
+      { return interfaces_ == nullptr ? nullptr : interfaces_->GetTypeList(); }
   uint32_t InterfacesOffset() { return interfaces_ == nullptr ? 0 : interfaces_->GetOffset(); }
   const StringId* SourceFile() const { return source_file_; }
   AnnotationsDirectoryItem* Annotations() const { return annotations_; }
diff --git a/dexlayout/dex_visualize.cc b/dexlayout/dex_visualize.cc
index 02274b2..75d47e4 100644
--- a/dexlayout/dex_visualize.cc
+++ b/dexlayout/dex_visualize.cc
@@ -31,7 +31,7 @@
 
 #include "dex_ir.h"
 #include "dexlayout.h"
-#include "jit/offline_profiling_info.h"
+#include "jit/profile_compilation_info.h"
 
 namespace art {
 
diff --git a/dexlayout/dexlayout.cc b/dexlayout/dexlayout.cc
index cac6090..1add6bf 100644
--- a/dexlayout/dexlayout.cc
+++ b/dexlayout/dexlayout.cc
@@ -37,7 +37,7 @@
 #include "dex_instruction-inl.h"
 #include "dex_visualize.h"
 #include "dex_writer.h"
-#include "jit/offline_profiling_info.h"
+#include "jit/profile_compilation_info.h"
 #include "mem_map.h"
 #include "os.h"
 #include "utils.h"
diff --git a/dexlayout/dexlayout_main.cc b/dexlayout/dexlayout_main.cc
index 5f8a118..ad599ae 100644
--- a/dexlayout/dexlayout_main.cc
+++ b/dexlayout/dexlayout_main.cc
@@ -30,7 +30,7 @@
 #include <fcntl.h>
 
 #include "base/logging.h"
-#include "jit/offline_profiling_info.h"
+#include "jit/profile_compilation_info.h"
 #include "runtime.h"
 #include "mem_map.h"
 
diff --git a/dexoptanalyzer/Android.bp b/dexoptanalyzer/Android.bp
new file mode 100644
index 0000000..cf4c99e
--- /dev/null
+++ b/dexoptanalyzer/Android.bp
@@ -0,0 +1,68 @@
+//
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+cc_defaults {
+    name: "dexoptanalyzer-defaults",
+    host_supported: true,
+    defaults: ["art_defaults"],
+    srcs: [
+        "dexoptanalyzer.cc",
+    ],
+
+    target: {
+        android: {
+            compile_multilib: "prefer32",
+        },
+    },
+
+    include_dirs: [
+        "art/cmdline",
+    ],
+
+    shared_libs: [
+        "libbase",
+    ],
+}
+
+art_cc_binary {
+    name: "dexoptanalyzer",
+    defaults: ["dexoptanalyzer-defaults"],
+    shared_libs: [
+        "libart",
+    ],
+}
+
+art_cc_binary {
+    name: "dexoptanalyzerd",
+    defaults: [
+        "dexoptanalyzer-defaults",
+        "art_debug_defaults",
+    ],
+    shared_libs: [
+        "libartd",
+    ],
+}
+
+art_cc_test {
+    name: "art_dexoptanalyzer_tests",
+    defaults: [
+        "art_gtest_defaults",
+    ],
+    shared_libs: [
+        "libbacktrace"
+    ],
+    srcs: ["dexoptanalyzer_test.cc"],
+}
diff --git a/dexoptanalyzer/dexoptanalyzer.cc b/dexoptanalyzer/dexoptanalyzer.cc
new file mode 100644
index 0000000..965e407
--- /dev/null
+++ b/dexoptanalyzer/dexoptanalyzer.cc
@@ -0,0 +1,265 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <string>
+
+#include "android-base/stringprintf.h"
+#include "android-base/strings.h"
+#include "compiler_filter.h"
+#include "dex_file.h"
+#include "noop_compiler_callbacks.h"
+#include "oat_file_assistant.h"
+#include "os.h"
+#include "runtime.h"
+#include "thread-inl.h"
+#include "utils.h"
+
+namespace art {
+
+// See OatFileAssistant docs for the meaning of the valid return codes.
+enum ReturnCodes {
+  kNoDexOptNeeded = 0,
+  kDex2OatFromScratch = 1,
+  kDex2OatForBootImageOat = 2,
+  kDex2OatForFilterOat = 3,
+  kDex2OatForRelocationOat = 4,
+  kDex2OatForBootImageOdex = 5,
+  kDex2OatForFilterOdex = 6,
+  kDex2OatForRelocationOdex = 7,
+
+  kErrorInvalidArguments = 101,
+  kErrorCannotCreateRuntime = 102,
+  kErrorUnknownDexOptNeeded = 103
+};
+
+static int original_argc;
+static char** original_argv;
+
+static std::string CommandLine() {
+  std::vector<std::string> command;
+  for (int i = 0; i < original_argc; ++i) {
+    command.push_back(original_argv[i]);
+  }
+  return android::base::Join(command, ' ');
+}
+
+static void UsageErrorV(const char* fmt, va_list ap) {
+  std::string error;
+  android::base::StringAppendV(&error, fmt, ap);
+  LOG(ERROR) << error;
+}
+
+static void UsageError(const char* fmt, ...) {
+  va_list ap;
+  va_start(ap, fmt);
+  UsageErrorV(fmt, ap);
+  va_end(ap);
+}
+
+NO_RETURN static void Usage(const char *fmt, ...) {
+  va_list ap;
+  va_start(ap, fmt);
+  UsageErrorV(fmt, ap);
+  va_end(ap);
+
+  UsageError("Command: %s", CommandLine().c_str());
+  UsageError("  Performs a dexopt analysis on the given dex file and returns whether or not");
+  UsageError("  the dex file needs to be dexopted.");
+  UsageError("Usage: dexoptanalyzer [options]...");
+  UsageError("");
+  UsageError("  --dex-file=<filename>: the dex file which should be analyzed.");
+  UsageError("");
+  UsageError("  --isa=<string>: the instruction set for which the analysis should be performed.");
+  UsageError("");
+  UsageError("  --compiler-filter=<string>: the target compiler filter to be used as reference");
+  UsageError("       when deciding if the dex file needs to be optimized.");
+  UsageError("");
+  UsageError("  --assume-profile-changed: assumes the profile information has changed");
+  UsageError("       when deciding if the dex file needs to be optimized.");
+  UsageError("");
+  UsageError("  --image=<filename>: optional, the image to be used to decide if the associated");
+  UsageError("       oat file is up to date. Defaults to $ANDROID_ROOT/framework/boot.art.");
+  UsageError("       Example: --image=/system/framework/boot.art");
+  UsageError("");
+  UsageError("  --android-data=<directory>: optional, the directory which should be used as");
+  UsageError("       android-data. By default ANDROID_DATA env variable is used.");
+  UsageError("");
+  UsageError("Return code:");
+  UsageError("  To make it easier to integrate with the internal tools this command will make");
+  UsageError("    available its result (dexoptNeeded) as the exit/return code. i.e. it will not");
+  UsageError("    return 0 for success and a non zero values for errors as the conventional");
+  UsageError("    commands. The following return codes are possible:");
+  UsageError("        kNoDexOptNeeded = 0");
+  UsageError("        kDex2OatFromScratch = 1");
+  UsageError("        kDex2OatForBootImageOat = 2");
+  UsageError("        kDex2OatForFilterOat = 3");
+  UsageError("        kDex2OatForRelocationOat = 4");
+  UsageError("        kDex2OatForBootImageOdex = 5");
+  UsageError("        kDex2OatForFilterOdex = 6");
+  UsageError("        kDex2OatForRelocationOdex = 7");
+
+  UsageError("        kErrorInvalidArguments = 101");
+  UsageError("        kErrorCannotCreateRuntime = 102");
+  UsageError("        kErrorUnknownDexOptNeeded = 103");
+  UsageError("");
+
+  exit(kErrorInvalidArguments);
+}
+
+class DexoptAnalyzer FINAL {
+ public:
+  DexoptAnalyzer() : assume_profile_changed_(false) {}
+
+  void ParseArgs(int argc, char **argv) {
+    original_argc = argc;
+    original_argv = argv;
+
+    InitLogging(argv, Runtime::Aborter);
+    // Skip over the command name.
+    argv++;
+    argc--;
+
+    if (argc == 0) {
+      Usage("No arguments specified");
+    }
+
+    for (int i = 0; i < argc; ++i) {
+      const StringPiece option(argv[i]);
+      if (option == "--assume-profile-changed") {
+        assume_profile_changed_ = true;
+      } else if (option.starts_with("--dex-file=")) {
+        dex_file_ = option.substr(strlen("--dex-file=")).ToString();
+      } else if (option.starts_with("--compiler-filter=")) {
+        std::string filter_str = option.substr(strlen("--compiler-filter=")).ToString();
+        if (!CompilerFilter::ParseCompilerFilter(filter_str.c_str(), &compiler_filter_)) {
+          Usage("Invalid compiler filter '%s'", option.data());
+        }
+      } else if (option.starts_with("--isa=")) {
+        std::string isa_str = option.substr(strlen("--isa=")).ToString();
+        isa_ = GetInstructionSetFromString(isa_str.c_str());
+        if (isa_ == kNone) {
+          Usage("Invalid isa '%s'", option.data());
+        }
+      } else if (option.starts_with("--image=")) {
+        image_ = option.substr(strlen("--image=")).ToString();
+      } else if (option.starts_with("--android-data=")) {
+        // Overwrite android-data if needed (oat file assistant relies on a valid directory to
+        // compute dalvik-cache folder). This is mostly used in tests.
+        std::string new_android_data = option.substr(strlen("--android-data=")).ToString();
+        setenv("ANDROID_DATA", new_android_data.c_str(), 1);
+      } else {
+        Usage("Unknown argument '%s'", option.data());
+      }
+    }
+
+    if (image_.empty()) {
+      // If we don't receive the image, try to use the default one.
+      // Tests may specify a different image (e.g. core image).
+      std::string error_msg;
+      image_ = GetDefaultBootImageLocation(&error_msg);
+
+      if (image_.empty()) {
+        LOG(ERROR) << error_msg;
+        Usage("--image unspecified and ANDROID_ROOT not set or image file does not exist.");
+      }
+    }
+  }
+
+  bool CreateRuntime() {
+    RuntimeOptions options;
+    // The image could be custom, so make sure we explicitly pass it.
+    std::string img = "-Ximage:" + image_;
+    options.push_back(std::make_pair(img.c_str(), nullptr));
+    // The instruction set of the image should match the instruction set we will test.
+    const void* isa_opt = reinterpret_cast<const void*>(GetInstructionSetString(isa_));
+    options.push_back(std::make_pair("imageinstructionset", isa_opt));
+     // Disable libsigchain. We don't don't need it to evaluate DexOptNeeded status.
+    options.push_back(std::make_pair("-Xno-sig-chain", nullptr));
+    // Pretend we are a compiler so that we can re-use the same infrastructure to load a different
+    // ISA image and minimize the amount of things that get started.
+    NoopCompilerCallbacks callbacks;
+    options.push_back(std::make_pair("compilercallbacks", &callbacks));
+    // Make sure we don't attempt to relocate. The tool should only retrieve the DexOptNeeded
+    // status and not attempt to relocate the boot image.
+    options.push_back(std::make_pair("-Xnorelocate", nullptr));
+
+    if (!Runtime::Create(options, false)) {
+      LOG(ERROR) << "Unable to initialize runtime";
+      return false;
+    }
+    // Runtime::Create acquired the mutator_lock_ that is normally given away when we
+    // Runtime::Start. Give it away now.
+    Thread::Current()->TransitionFromRunnableToSuspended(kNative);
+
+    return true;
+  }
+
+  int GetDexOptNeeded() {
+    // If the file does not exist there's nothing to do.
+    // This is a fast path to avoid creating the runtime (b/34385298).
+    if (!OS::FileExists(dex_file_.c_str())) {
+      return kNoDexOptNeeded;
+    }
+    if (!CreateRuntime()) {
+      return kErrorCannotCreateRuntime;
+    }
+    OatFileAssistant oat_file_assistant(dex_file_.c_str(), isa_, /*load_executable*/ false);
+    // Always treat elements of the bootclasspath as up-to-date.
+    // TODO(calin): this check should be in OatFileAssistant.
+    if (oat_file_assistant.IsInBootClassPath()) {
+      return kNoDexOptNeeded;
+    }
+    int dexoptNeeded = oat_file_assistant.GetDexOptNeeded(
+        compiler_filter_, assume_profile_changed_);
+
+    // Convert OatFileAssitant codes to dexoptanalyzer codes.
+    switch (dexoptNeeded) {
+      case OatFileAssistant::kNoDexOptNeeded: return kNoDexOptNeeded;
+      case OatFileAssistant::kDex2OatFromScratch: return kDex2OatFromScratch;
+      case OatFileAssistant::kDex2OatForBootImage: return kDex2OatForBootImageOat;
+      case OatFileAssistant::kDex2OatForFilter: return kDex2OatForFilterOat;
+      case OatFileAssistant::kDex2OatForRelocation: return kDex2OatForRelocationOat;
+
+      case -OatFileAssistant::kDex2OatForBootImage: return kDex2OatForBootImageOdex;
+      case -OatFileAssistant::kDex2OatForFilter: return kDex2OatForFilterOdex;
+      case -OatFileAssistant::kDex2OatForRelocation: return kDex2OatForRelocationOdex;
+      default:
+        LOG(ERROR) << "Unknown dexoptNeeded " << dexoptNeeded;
+        return kErrorUnknownDexOptNeeded;
+    }
+  }
+
+ private:
+  std::string dex_file_;
+  InstructionSet isa_;
+  CompilerFilter::Filter compiler_filter_;
+  bool assume_profile_changed_;
+  std::string image_;
+};
+
+static int dexoptAnalyze(int argc, char** argv) {
+  DexoptAnalyzer analyzer;
+
+  // Parse arguments. Argument mistakes will lead to exit(kErrorInvalidArguments) in UsageError.
+  analyzer.ParseArgs(argc, argv);
+  return analyzer.GetDexOptNeeded();
+}
+
+}  // namespace art
+
+int main(int argc, char **argv) {
+  return art::dexoptAnalyze(argc, argv);
+}
diff --git a/dexoptanalyzer/dexoptanalyzer_test.cc b/dexoptanalyzer/dexoptanalyzer_test.cc
new file mode 100644
index 0000000..57d3f1f
--- /dev/null
+++ b/dexoptanalyzer/dexoptanalyzer_test.cc
@@ -0,0 +1,311 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gtest/gtest.h>
+
+#include "arch/instruction_set.h"
+#include "compiler_filter.h"
+#include "dexopt_test.h"
+
+namespace art {
+
+class DexoptAnalyzerTest : public DexoptTest {
+ protected:
+  std::string GetDexoptAnalyzerCmd() {
+    std::string file_path = GetTestAndroidRoot();
+    file_path += "/bin/dexoptanalyzer";
+    if (kIsDebugBuild) {
+      file_path += "d";
+    }
+    EXPECT_TRUE(OS::FileExists(file_path.c_str())) << file_path << " should be a valid file path";
+    return file_path;
+  }
+
+  int Analyze(const std::string& dex_file,
+              CompilerFilter::Filter compiler_filter,
+              bool assume_profile_changed) {
+    std::string dexoptanalyzer_cmd = GetDexoptAnalyzerCmd();
+    std::vector<std::string> argv_str;
+    argv_str.push_back(dexoptanalyzer_cmd);
+    argv_str.push_back("--dex-file=" + dex_file);
+    argv_str.push_back("--isa=" + std::string(GetInstructionSetString(kRuntimeISA)));
+    argv_str.push_back("--compiler-filter=" + CompilerFilter::NameOfFilter(compiler_filter));
+    if (assume_profile_changed) {
+      argv_str.push_back("--assume-profile-changed");
+    }
+    argv_str.push_back("--image=" + GetImageLocation());
+    argv_str.push_back("--android-data=" + android_data_);
+
+    std::string error;
+    return ExecAndReturnCode(argv_str, &error);
+  }
+
+  int DexoptanalyzerToOatFileAssistant(int dexoptanalyzerResult) {
+    switch (dexoptanalyzerResult) {
+      case 0: return OatFileAssistant::kNoDexOptNeeded;
+      case 1: return OatFileAssistant::kDex2OatFromScratch;
+      case 2: return OatFileAssistant::kDex2OatForBootImage;
+      case 3: return OatFileAssistant::kDex2OatForFilter;
+      case 4: return OatFileAssistant::kDex2OatForRelocation;
+      case 5: return -OatFileAssistant::kDex2OatForBootImage;
+      case 6: return -OatFileAssistant::kDex2OatForFilter;
+      case 7: return -OatFileAssistant::kDex2OatForRelocation;
+      default: return dexoptanalyzerResult;
+    }
+  }
+
+  // Verify that the output of dexoptanalyzer for the given arguments is the same
+  // as the output of OatFileAssistant::GetDexOptNeeded.
+  void Verify(const std::string& dex_file,
+              CompilerFilter::Filter compiler_filter,
+              bool assume_profile_changed = false) {
+    int dexoptanalyzerResult = Analyze(dex_file, compiler_filter, assume_profile_changed);
+    dexoptanalyzerResult = DexoptanalyzerToOatFileAssistant(dexoptanalyzerResult);
+    OatFileAssistant oat_file_assistant(dex_file.c_str(), kRuntimeISA, /*load_executable*/ false);
+    int assistantResult = oat_file_assistant.GetDexOptNeeded(
+        compiler_filter, assume_profile_changed);
+    EXPECT_EQ(assistantResult, dexoptanalyzerResult);
+  }
+};
+
+// The tests below exercise the same test case from oat_file_assistant_test.cc.
+
+// Case: We have a DEX file, but no OAT file for it.
+TEST_F(DexoptAnalyzerTest, DexNoOat) {
+  std::string dex_location = GetScratchDir() + "/DexNoOat.jar";
+  Copy(GetDexSrc1(), dex_location);
+
+  Verify(dex_location, CompilerFilter::kSpeed);
+  Verify(dex_location, CompilerFilter::kVerifyAtRuntime);
+  Verify(dex_location, CompilerFilter::kInterpretOnly);
+  Verify(dex_location, CompilerFilter::kSpeedProfile);
+}
+
+// Case: We have a DEX file and up-to-date OAT file for it.
+TEST_F(DexoptAnalyzerTest, OatUpToDate) {
+  std::string dex_location = GetScratchDir() + "/OatUpToDate.jar";
+  Copy(GetDexSrc1(), dex_location);
+  GenerateOatForTest(dex_location.c_str(), CompilerFilter::kSpeed);
+
+  Verify(dex_location, CompilerFilter::kSpeed);
+  Verify(dex_location, CompilerFilter::kInterpretOnly);
+  Verify(dex_location, CompilerFilter::kVerifyAtRuntime);
+  Verify(dex_location, CompilerFilter::kEverything);
+}
+
+// Case: We have a DEX file and speed-profile OAT file for it.
+TEST_F(DexoptAnalyzerTest, ProfileOatUpToDate) {
+  std::string dex_location = GetScratchDir() + "/ProfileOatUpToDate.jar";
+  Copy(GetDexSrc1(), dex_location);
+  GenerateOatForTest(dex_location.c_str(), CompilerFilter::kSpeedProfile);
+
+  Verify(dex_location, CompilerFilter::kSpeedProfile, false);
+  Verify(dex_location, CompilerFilter::kInterpretOnly, false);
+  Verify(dex_location, CompilerFilter::kSpeedProfile, true);
+  Verify(dex_location, CompilerFilter::kInterpretOnly, true);
+}
+
+// Case: We have a MultiDEX file and up-to-date OAT file for it.
+TEST_F(DexoptAnalyzerTest, MultiDexOatUpToDate) {
+  std::string dex_location = GetScratchDir() + "/MultiDexOatUpToDate.jar";
+  Copy(GetMultiDexSrc1(), dex_location);
+  GenerateOatForTest(dex_location.c_str(), CompilerFilter::kSpeed);
+
+  Verify(dex_location, CompilerFilter::kSpeed, false);
+}
+
+// Case: We have a MultiDEX file where the secondary dex file is out of date.
+TEST_F(DexoptAnalyzerTest, MultiDexSecondaryOutOfDate) {
+  std::string dex_location = GetScratchDir() + "/MultiDexSecondaryOutOfDate.jar";
+
+  // Compile code for GetMultiDexSrc1.
+  Copy(GetMultiDexSrc1(), dex_location);
+  GenerateOatForTest(dex_location.c_str(), CompilerFilter::kSpeed);
+
+  // Now overwrite the dex file with GetMultiDexSrc2 so the secondary checksum
+  // is out of date.
+  Copy(GetMultiDexSrc2(), dex_location);
+
+  Verify(dex_location, CompilerFilter::kSpeed, false);
+}
+
+
+// Case: We have a DEX file and an OAT file out of date with respect to the
+// dex checksum.
+TEST_F(DexoptAnalyzerTest, OatDexOutOfDate) {
+  std::string dex_location = GetScratchDir() + "/OatDexOutOfDate.jar";
+
+  // We create a dex, generate an oat for it, then overwrite the dex with a
+  // different dex to make the oat out of date.
+  Copy(GetDexSrc1(), dex_location);
+  GenerateOatForTest(dex_location.c_str(), CompilerFilter::kSpeed);
+  Copy(GetDexSrc2(), dex_location);
+
+  Verify(dex_location, CompilerFilter::kVerifyAtRuntime);
+  Verify(dex_location, CompilerFilter::kSpeed);
+}
+
+// Case: We have a DEX file and an OAT file out of date with respect to the
+// boot image.
+TEST_F(DexoptAnalyzerTest, OatImageOutOfDate) {
+  std::string dex_location = GetScratchDir() + "/OatImageOutOfDate.jar";
+
+  Copy(GetDexSrc1(), dex_location);
+  GenerateOatForTest(dex_location.c_str(),
+                     CompilerFilter::kSpeed,
+                     /*relocate*/true,
+                     /*pic*/false,
+                     /*with_alternate_image*/true);
+
+  Verify(dex_location, CompilerFilter::kVerifyAtRuntime);
+  Verify(dex_location, CompilerFilter::kInterpretOnly);
+  Verify(dex_location, CompilerFilter::kSpeed);
+}
+
+// Case: We have a DEX file and a verify-at-runtime OAT file out of date with
+// respect to the boot image.
+// It shouldn't matter that the OAT file is out of date, because it is
+// verify-at-runtime.
+TEST_F(DexoptAnalyzerTest, OatVerifyAtRuntimeImageOutOfDate) {
+  std::string dex_location = GetScratchDir() + "/OatVerifyAtRuntimeImageOutOfDate.jar";
+
+  Copy(GetDexSrc1(), dex_location);
+  GenerateOatForTest(dex_location.c_str(),
+                     CompilerFilter::kVerifyAtRuntime,
+                     /*relocate*/true,
+                     /*pic*/false,
+                     /*with_alternate_image*/true);
+
+  Verify(dex_location, CompilerFilter::kVerifyAtRuntime);
+  Verify(dex_location, CompilerFilter::kInterpretOnly);
+}
+
+// Case: We have a DEX file and an ODEX file, but no OAT file.
+TEST_F(DexoptAnalyzerTest, DexOdexNoOat) {
+  std::string dex_location = GetScratchDir() + "/DexOdexNoOat.jar";
+  std::string odex_location = GetOdexDir() + "/DexOdexNoOat.odex";
+
+  Copy(GetDexSrc1(), dex_location);
+  GenerateOdexForTest(dex_location, odex_location, CompilerFilter::kSpeed);
+
+  Verify(dex_location, CompilerFilter::kVerifyAtRuntime);
+  Verify(dex_location, CompilerFilter::kSpeed);
+}
+
+// Case: We have a stripped DEX file and a PIC ODEX file, but no OAT file.
+TEST_F(DexoptAnalyzerTest, StrippedDexOdexNoOat) {
+  std::string dex_location = GetScratchDir() + "/StrippedDexOdexNoOat.jar";
+  std::string odex_location = GetOdexDir() + "/StrippedDexOdexNoOat.odex";
+
+  Copy(GetDexSrc1(), dex_location);
+  GeneratePicOdexForTest(dex_location, odex_location, CompilerFilter::kSpeed);
+
+  // Strip the dex file
+  Copy(GetStrippedDexSrc1(), dex_location);
+
+  Verify(dex_location, CompilerFilter::kSpeed);
+}
+
+// Case: We have a stripped DEX file, a PIC ODEX file, and an out-of-date OAT file.
+TEST_F(DexoptAnalyzerTest, StrippedDexOdexOat) {
+  std::string dex_location = GetScratchDir() + "/StrippedDexOdexOat.jar";
+  std::string odex_location = GetOdexDir() + "/StrippedDexOdexOat.odex";
+
+  // Create the oat file from a different dex file so it looks out of date.
+  Copy(GetDexSrc2(), dex_location);
+  GenerateOatForTest(dex_location.c_str(), CompilerFilter::kSpeed);
+
+  // Create the odex file
+  Copy(GetDexSrc1(), dex_location);
+  GeneratePicOdexForTest(dex_location, odex_location, CompilerFilter::kSpeed);
+
+  // Strip the dex file.
+  Copy(GetStrippedDexSrc1(), dex_location);
+
+  Verify(dex_location, CompilerFilter::kVerifyAtRuntime);
+  Verify(dex_location, CompilerFilter::kSpeed);
+  Verify(dex_location, CompilerFilter::kEverything);
+}
+
+// Case: We have a stripped (or resource-only) DEX file, no ODEX file and no
+// OAT file. Expect: The status is kNoDexOptNeeded.
+TEST_F(DexoptAnalyzerTest, ResourceOnlyDex) {
+  std::string dex_location = GetScratchDir() + "/ResourceOnlyDex.jar";
+
+  Copy(GetStrippedDexSrc1(), dex_location);
+
+  Verify(dex_location, CompilerFilter::kSpeed);
+  Verify(dex_location, CompilerFilter::kVerifyAtRuntime);
+  Verify(dex_location, CompilerFilter::kInterpretOnly);
+}
+
+// Case: We have a DEX file, an ODEX file and an OAT file, where the ODEX and
+// OAT files both have patch delta of 0.
+TEST_F(DexoptAnalyzerTest, OdexOatOverlap) {
+  std::string dex_location = GetScratchDir() + "/OdexOatOverlap.jar";
+  std::string odex_location = GetOdexDir() + "/OdexOatOverlap.odex";
+  std::string oat_location = GetOdexDir() + "/OdexOatOverlap.oat";
+
+  Copy(GetDexSrc1(), dex_location);
+  GenerateOdexForTest(dex_location, odex_location, CompilerFilter::kSpeed);
+
+  // Create the oat file by copying the odex so they are located in the same
+  // place in memory.
+  Copy(odex_location, oat_location);
+
+  Verify(dex_location, CompilerFilter::kSpeed);
+}
+
+// Case: We have a DEX file and a PIC ODEX file, but no OAT file.
+TEST_F(DexoptAnalyzerTest, DexPicOdexNoOat) {
+  std::string dex_location = GetScratchDir() + "/DexPicOdexNoOat.jar";
+  std::string odex_location = GetOdexDir() + "/DexPicOdexNoOat.odex";
+
+  Copy(GetDexSrc1(), dex_location);
+  GeneratePicOdexForTest(dex_location, odex_location, CompilerFilter::kSpeed);
+
+  Verify(dex_location, CompilerFilter::kSpeed);
+  Verify(dex_location, CompilerFilter::kEverything);
+}
+
+// Case: We have a DEX file and a VerifyAtRuntime ODEX file, but no OAT file..
+TEST_F(DexoptAnalyzerTest, DexVerifyAtRuntimeOdexNoOat) {
+  std::string dex_location = GetScratchDir() + "/DexVerifyAtRuntimeOdexNoOat.jar";
+  std::string odex_location = GetOdexDir() + "/DexVerifyAtRuntimeOdexNoOat.odex";
+
+  Copy(GetDexSrc1(), dex_location);
+  GenerateOdexForTest(dex_location, odex_location, CompilerFilter::kVerifyAtRuntime);
+
+  Verify(dex_location, CompilerFilter::kVerifyAtRuntime);
+  Verify(dex_location, CompilerFilter::kSpeed);
+}
+
+// Case: Non-standard extension for dex file.
+TEST_F(DexoptAnalyzerTest, LongDexExtension) {
+  std::string dex_location = GetScratchDir() + "/LongDexExtension.jarx";
+  Copy(GetDexSrc1(), dex_location);
+
+  Verify(dex_location, CompilerFilter::kSpeed);
+}
+
+// Case: Very short, non-existent Dex location.
+TEST_F(DexoptAnalyzerTest, ShortDexLocation) {
+  std::string dex_location = "/xx";
+
+  Verify(dex_location, CompilerFilter::kSpeed);
+}
+
+}  // namespace art
diff --git a/oatdump/oatdump.cc b/oatdump/oatdump.cc
index 148ee88..69901c1 100644
--- a/oatdump/oatdump.cc
+++ b/oatdump/oatdump.cc
@@ -529,6 +529,12 @@
       }
     }
 
+    {
+      os << "OAT FILE STATS:\n";
+      VariableIndentationOutputStream vios(&os);
+      stats_.Dump(vios);
+    }
+
     os << std::flush;
     return success;
   }
@@ -574,6 +580,116 @@
     return nullptr;
   }
 
+  struct Stats {
+    enum ByteKind {
+      kByteKindCode,
+      kByteKindQuickMethodHeader,
+      kByteKindCodeInfoLocationCatalog,
+      kByteKindCodeInfoDexRegisterMap,
+      kByteKindCodeInfoInlineInfo,
+      kByteKindCodeInfoEncoding,
+      kByteKindCodeInfoOther,
+      kByteKindStackMapNativePc,
+      kByteKindStackMapDexPc,
+      kByteKindStackMapDexRegisterMap,
+      kByteKindStackMapInlineInfo,
+      kByteKindStackMapRegisterMask,
+      kByteKindStackMapMask,
+      kByteKindStackMapOther,
+      kByteKindCount,
+      kByteKindStackMapFirst = kByteKindCodeInfoOther,
+      kByteKindStackMapLast = kByteKindStackMapOther,
+    };
+    int64_t bits[kByteKindCount] = {};
+    // Since code has deduplication, seen tracks already seen pointers to avoid double counting
+    // deduplicated code and tables.
+    std::unordered_set<const void*> seen;
+
+    // Returns true if it was newly added.
+    bool AddBitsIfUnique(ByteKind kind, int64_t count, const void* address) {
+      if (seen.insert(address).second == true) {
+        // True means the address was not already in the set.
+        AddBits(kind, count);
+        return true;
+      }
+      return false;
+    }
+
+    void AddBits(ByteKind kind, int64_t count) {
+      bits[kind] += count;
+    }
+
+    void Dump(VariableIndentationOutputStream& os) {
+      const int64_t sum = std::accumulate(bits, bits + kByteKindCount, 0u);
+      os.Stream() << "Dumping cumulative use of " << sum / kBitsPerByte << " accounted bytes\n";
+      if (sum > 0) {
+        const int64_t stack_map_bits = std::accumulate(bits + kByteKindStackMapFirst,
+                                                       bits + kByteKindStackMapLast + 1,
+                                                       0u);
+        Dump(os, "Code                           ", bits[kByteKindCode], sum);
+        Dump(os, "QuickMethodHeader              ", bits[kByteKindQuickMethodHeader], sum);
+        Dump(os, "CodeInfoEncoding               ", bits[kByteKindCodeInfoEncoding], sum);
+        Dump(os, "CodeInfoLocationCatalog        ", bits[kByteKindCodeInfoLocationCatalog], sum);
+        Dump(os, "CodeInfoDexRegisterMap         ", bits[kByteKindCodeInfoDexRegisterMap], sum);
+        Dump(os, "CodeInfoInlineInfo             ", bits[kByteKindCodeInfoInlineInfo], sum);
+        Dump(os, "CodeInfoStackMap               ", stack_map_bits, sum);
+        {
+          ScopedIndentation indent1(&os);
+          Dump(os,
+               "StackMapNativePc             ",
+               bits[kByteKindStackMapNativePc],
+               stack_map_bits,
+               "stack map");
+          Dump(os,
+               "StackMapDexPcEncoding        ",
+               bits[kByteKindStackMapDexPc],
+               stack_map_bits,
+               "stack map");
+          Dump(os,
+               "StackMapDexRegisterMap       ",
+               bits[kByteKindStackMapDexRegisterMap],
+               stack_map_bits,
+               "stack map");
+          Dump(os,
+               "StackMapInlineInfo           ",
+               bits[kByteKindStackMapInlineInfo],
+               stack_map_bits,
+               "stack map");
+          Dump(os,
+               "StackMapRegisterMaskEncoding ",
+               bits[kByteKindStackMapRegisterMask],
+               stack_map_bits,
+               "stack map");
+          Dump(os,
+               "StackMapMask                 ",
+               bits[kByteKindStackMapMask],
+               stack_map_bits,
+               "stack map");
+          Dump(os,
+               "StackMapOther                ",
+               bits[kByteKindStackMapOther],
+               stack_map_bits,
+               "stack map");
+        }
+      }
+      os.Stream() << "\n" << std::flush;
+    }
+
+   private:
+    void Dump(VariableIndentationOutputStream& os,
+              const char* name,
+              int64_t size,
+              int64_t total,
+              const char* sum_of = "total") {
+      const double percent = (static_cast<double>(size) / static_cast<double>(total)) * 100;
+      os.Stream() << StringPrintf("%s = %8" PRId64 " (%2.0f%% of %s)\n",
+                                  name,
+                                  size / kBitsPerByte,
+                                  percent,
+                                  sum_of);
+    }
+  };
+
  private:
   void AddAllOffsets() {
     // We don't know the length of the code for each method, but we need to know where to stop
@@ -1046,7 +1162,9 @@
       vios->Stream() << "OatQuickMethodHeader ";
       uint32_t method_header_offset = oat_method.GetOatQuickMethodHeaderOffset();
       const OatQuickMethodHeader* method_header = oat_method.GetOatQuickMethodHeader();
-
+      stats_.AddBitsIfUnique(Stats::kByteKindQuickMethodHeader,
+                             sizeof(*method_header) * kBitsPerByte,
+                             method_header);
       if (options_.absolute_addresses_) {
         vios->Stream() << StringPrintf("%p ", method_header);
       }
@@ -1118,6 +1236,7 @@
         const void* code = oat_method.GetQuickCode();
         uint32_t aligned_code_begin = AlignCodeOffset(code_offset);
         uint64_t aligned_code_end = aligned_code_begin + code_size;
+        stats_.AddBitsIfUnique(Stats::kByteKindCode, code_size * kBitsPerByte, code);
 
         if (options_.absolute_addresses_) {
           vios->Stream() << StringPrintf("%p ", code);
@@ -1223,7 +1342,8 @@
     code_info.Dump(vios,
                    oat_method.GetCodeOffset(),
                    code_item.registers_size_,
-                   options_.dump_code_info_stack_maps_);
+                   options_.dump_code_info_stack_maps_,
+                   instruction_set_);
   }
 
   void DumpVregLocations(std::ostream& os, const OatFile::OatMethod& oat_method,
@@ -1329,21 +1449,22 @@
   // For identical native PCs, the order from the CodeInfo is preserved.
   class StackMapsHelper {
    public:
-    explicit StackMapsHelper(const uint8_t* raw_code_info)
+    explicit StackMapsHelper(const uint8_t* raw_code_info, InstructionSet instruction_set)
         : code_info_(raw_code_info),
           encoding_(code_info_.ExtractEncoding()),
           number_of_stack_maps_(code_info_.GetNumberOfStackMaps(encoding_)),
           indexes_(),
-          offset_(static_cast<size_t>(-1)),
-          stack_map_index_(0u) {
+          offset_(static_cast<uint32_t>(-1)),
+          stack_map_index_(0u),
+          instruction_set_(instruction_set) {
       if (number_of_stack_maps_ != 0u) {
         // Check if native PCs are ordered.
         bool ordered = true;
         StackMap last = code_info_.GetStackMapAt(0u, encoding_);
         for (size_t i = 1; i != number_of_stack_maps_; ++i) {
           StackMap current = code_info_.GetStackMapAt(i, encoding_);
-          if (last.GetNativePcOffset(encoding_.stack_map_encoding) >
-              current.GetNativePcOffset(encoding_.stack_map_encoding)) {
+          if (last.GetNativePcOffset(encoding_.stack_map_encoding, instruction_set) >
+              current.GetNativePcOffset(encoding_.stack_map_encoding, instruction_set)) {
             ordered = false;
             break;
           }
@@ -1359,14 +1480,17 @@
                     indexes_.end(),
                     [this](size_t lhs, size_t rhs) {
                       StackMap left = code_info_.GetStackMapAt(lhs, encoding_);
-                      uint32_t left_pc = left.GetNativePcOffset(encoding_.stack_map_encoding);
+                      uint32_t left_pc = left.GetNativePcOffset(encoding_.stack_map_encoding,
+                                                                instruction_set_);
                       StackMap right = code_info_.GetStackMapAt(rhs, encoding_);
-                      uint32_t right_pc = right.GetNativePcOffset(encoding_.stack_map_encoding);
+                      uint32_t right_pc = right.GetNativePcOffset(encoding_.stack_map_encoding,
+                                                                  instruction_set_);
                       // If the PCs are the same, compare indexes to preserve the original order.
                       return (left_pc < right_pc) || (left_pc == right_pc && lhs < rhs);
                     });
         }
-        offset_ = GetStackMapAt(0).GetNativePcOffset(encoding_.stack_map_encoding);
+        offset_ = GetStackMapAt(0).GetNativePcOffset(encoding_.stack_map_encoding,
+                                                     instruction_set_);
       }
     }
 
@@ -1378,7 +1502,7 @@
       return encoding_;
     }
 
-    size_t GetOffset() const {
+    uint32_t GetOffset() const {
       return offset_;
     }
 
@@ -1389,8 +1513,9 @@
     void Next() {
       ++stack_map_index_;
       offset_ = (stack_map_index_ == number_of_stack_maps_)
-          ? static_cast<size_t>(-1)
-          : GetStackMapAt(stack_map_index_).GetNativePcOffset(encoding_.stack_map_encoding);
+          ? static_cast<uint32_t>(-1)
+          : GetStackMapAt(stack_map_index_).GetNativePcOffset(encoding_.stack_map_encoding,
+                                                              instruction_set_);
     }
 
    private:
@@ -1406,8 +1531,9 @@
     const CodeInfoEncoding encoding_;
     const size_t number_of_stack_maps_;
     dchecked_vector<size_t> indexes_;  // Used if stack map native PCs are not ordered.
-    size_t offset_;
+    uint32_t offset_;
     size_t stack_map_index_;
+    const InstructionSet instruction_set_;
   };
 
   void DumpCode(VariableIndentationOutputStream* vios,
@@ -1423,7 +1549,61 @@
       return;
     } else if (!bad_input && IsMethodGeneratedByOptimizingCompiler(oat_method, code_item)) {
       // The optimizing compiler outputs its CodeInfo data in the vmap table.
-      StackMapsHelper helper(oat_method.GetVmapTable());
+      StackMapsHelper helper(oat_method.GetVmapTable(), instruction_set_);
+      {
+        CodeInfoEncoding encoding(helper.GetEncoding());
+        StackMapEncoding stack_map_encoding(encoding.stack_map_encoding);
+        // helper.GetCodeInfo().GetStackMapAt(0, encoding).;
+        const size_t num_stack_maps = encoding.number_of_stack_maps;
+        std::vector<uint8_t> size_vector;
+        encoding.Compress(&size_vector);
+        if (stats_.AddBitsIfUnique(Stats::kByteKindCodeInfoEncoding,
+                                   size_vector.size() * kBitsPerByte,
+                                   oat_method.GetVmapTable())) {
+          stats_.AddBits(
+              Stats::kByteKindStackMapNativePc,
+              stack_map_encoding.GetNativePcEncoding().BitSize() * num_stack_maps);
+          stats_.AddBits(
+              Stats::kByteKindStackMapDexPc,
+              stack_map_encoding.GetDexPcEncoding().BitSize() * num_stack_maps);
+          stats_.AddBits(
+              Stats::kByteKindStackMapDexRegisterMap,
+              stack_map_encoding.GetDexRegisterMapEncoding().BitSize() * num_stack_maps);
+          stats_.AddBits(
+              Stats::kByteKindStackMapInlineInfo,
+              stack_map_encoding.GetInlineInfoEncoding().BitSize() * num_stack_maps);
+          stats_.AddBits(
+              Stats::kByteKindStackMapRegisterMask,
+              stack_map_encoding.GetRegisterMaskEncoding().BitSize() * num_stack_maps);
+          const size_t stack_mask_bits = encoding.stack_map_size_in_bytes * kBitsPerByte -
+              stack_map_encoding.GetStackMaskBitOffset();
+          stats_.AddBits(
+              Stats::kByteKindStackMapMask,
+              stack_mask_bits * num_stack_maps);
+          const size_t stack_map_bits =
+              stack_map_encoding.GetStackMaskBitOffset() + stack_mask_bits;
+          stats_.AddBits(
+              Stats::kByteKindStackMapOther,
+              (encoding.stack_map_size_in_bytes * kBitsPerByte - stack_map_bits) * num_stack_maps);
+          const size_t stack_map_bytes = helper.GetCodeInfo().GetStackMapsSize(encoding);
+          const size_t location_catalog_bytes =
+              helper.GetCodeInfo().GetDexRegisterLocationCatalogSize(encoding);
+          stats_.AddBits(Stats::kByteKindCodeInfoLocationCatalog,
+                         kBitsPerByte * location_catalog_bytes);
+          const size_t dex_register_bytes =
+              helper.GetCodeInfo().GetDexRegisterMapsSize(encoding, code_item->registers_size_);
+          stats_.AddBits(
+              Stats::kByteKindCodeInfoDexRegisterMap,
+              kBitsPerByte * dex_register_bytes);
+          const size_t inline_info_bytes =
+              encoding.non_header_size -
+              stack_map_bytes -
+              location_catalog_bytes -
+              dex_register_bytes;
+          stats_.AddBits(Stats::kByteKindCodeInfoInlineInfo,
+                         inline_info_bytes * kBitsPerByte);
+        }
+      }
       const uint8_t* quick_native_pc = reinterpret_cast<const uint8_t*>(quick_code);
       size_t offset = 0;
       while (offset < code_size) {
@@ -1436,7 +1616,8 @@
                          helper.GetCodeInfo(),
                          helper.GetEncoding(),
                          oat_method.GetCodeOffset(),
-                         code_item->registers_size_);
+                         code_item->registers_size_,
+                         instruction_set_);
           do {
             helper.Next();
             // There may be multiple stack maps at a given PC. We display only the first one.
@@ -1460,6 +1641,7 @@
   const InstructionSet instruction_set_;
   std::set<uintptr_t> offsets_;
   Disassembler* disassembler_;
+  Stats stats_;
 };
 
 class ImageDumper {
@@ -1776,7 +1958,7 @@
         os << StringPrintf("%d (0x%x)\n", field->GetShort(obj), field->GetShort(obj));
         break;
       case Primitive::kPrimBoolean:
-        os << StringPrintf("%s (0x%x)\n", field->GetBoolean(obj)? "true" : "false",
+        os << StringPrintf("%s (0x%x)\n", field->GetBoolean(obj) ? "true" : "false",
             field->GetBoolean(obj));
         break;
       case Primitive::kPrimByte:
@@ -2132,7 +2314,6 @@
 
     size_t managed_code_bytes;
     size_t managed_code_bytes_ignoring_deduplication;
-    size_t managed_to_native_code_bytes;
     size_t native_to_managed_code_bytes;
     size_t class_initializer_code_bytes;
     size_t large_initializer_code_bytes;
@@ -2161,7 +2342,6 @@
           alignment_bytes(0),
           managed_code_bytes(0),
           managed_code_bytes_ignoring_deduplication(0),
-          managed_to_native_code_bytes(0),
           native_to_managed_code_bytes(0),
           class_initializer_code_bytes(0),
           large_initializer_code_bytes(0),
@@ -2359,7 +2539,6 @@
 
       os << StringPrintf("oat_file_bytes               = %8zd\n"
                          "managed_code_bytes           = %8zd (%2.0f%% of oat file bytes)\n"
-                         "managed_to_native_code_bytes = %8zd (%2.0f%% of oat file bytes)\n"
                          "native_to_managed_code_bytes = %8zd (%2.0f%% of oat file bytes)\n\n"
                          "class_initializer_code_bytes = %8zd (%2.0f%% of oat file bytes)\n"
                          "large_initializer_code_bytes = %8zd (%2.0f%% of oat file bytes)\n"
@@ -2367,8 +2546,6 @@
                          oat_file_bytes,
                          managed_code_bytes,
                          PercentOfOatBytes(managed_code_bytes),
-                         managed_to_native_code_bytes,
-                         PercentOfOatBytes(managed_to_native_code_bytes),
                          native_to_managed_code_bytes,
                          PercentOfOatBytes(native_to_managed_code_bytes),
                          class_initializer_code_bytes,
diff --git a/oatdump/oatdump_test.cc b/oatdump/oatdump_test.cc
index e77d03b..ba57d18 100644
--- a/oatdump/oatdump_test.cc
+++ b/oatdump/oatdump_test.cc
@@ -102,6 +102,7 @@
         // Code and dex code do not show up if list only.
         expected_prefixes.push_back("DEX CODE:");
         expected_prefixes.push_back("CODE:");
+        expected_prefixes.push_back("CodeInfoEncoding");
       }
       if (mode == kModeArt) {
         exec_argv.push_back("--image=" + core_art_location_);
diff --git a/profman/profile_assistant.h b/profman/profile_assistant.h
index d3c75b8..be703ab 100644
--- a/profman/profile_assistant.h
+++ b/profman/profile_assistant.h
@@ -21,7 +21,7 @@
 #include <vector>
 
 #include "base/scoped_flock.h"
-#include "jit/offline_profiling_info.h"
+#include "jit/profile_compilation_info.h"
 
 namespace art {
 
diff --git a/profman/profile_assistant_test.cc b/profman/profile_assistant_test.cc
index 776c31a..2f40fef 100644
--- a/profman/profile_assistant_test.cc
+++ b/profman/profile_assistant_test.cc
@@ -19,7 +19,7 @@
 #include "base/unix_file/fd_file.h"
 #include "common_runtime_test.h"
 #include "profile_assistant.h"
-#include "jit/offline_profiling_info.h"
+#include "jit/profile_compilation_info.h"
 #include "utils.h"
 
 namespace art {
diff --git a/profman/profman.cc b/profman/profman.cc
index e538407..ffebb6a 100644
--- a/profman/profman.cc
+++ b/profman/profman.cc
@@ -34,7 +34,7 @@
 #include "base/time_utils.h"
 #include "base/unix_file/fd_file.h"
 #include "dex_file.h"
-#include "jit/offline_profiling_info.h"
+#include "jit/profile_compilation_info.h"
 #include "runtime.h"
 #include "utils.h"
 #include "zip_archive.h"
diff --git a/runtime/Android.bp b/runtime/Android.bp
index 86019bf..7f98513 100644
--- a/runtime/Android.bp
+++ b/runtime/Android.bp
@@ -112,7 +112,7 @@
         "jit/debugger_interface.cc",
         "jit/jit.cc",
         "jit/jit_code_cache.cc",
-        "jit/offline_profiling_info.cc",
+        "jit/profile_compilation_info.cc",
         "jit/profiling_info.cc",
         "jit/profile_saver.cc",
         "jni_internal.cc",
@@ -184,6 +184,7 @@
         "reference_table.cc",
         "reflection.cc",
         "runtime.cc",
+        "runtime_callbacks.cc",
         "runtime_options.cc",
         "signal_catcher.cc",
         "stack.cc",
@@ -473,10 +474,14 @@
 art_cc_library {
     name: "libart-runtime-gtest",
     defaults: ["libart-gtest-defaults"],
-    srcs: ["common_runtime_test.cc"],
+    srcs: [
+        "common_runtime_test.cc",
+        "dexopt_test.cc"
+    ],
     shared_libs: [
         "libartd",
         "libbase",
+        "libbacktrace"
     ],
 }
 
@@ -563,6 +568,7 @@
         "parsed_options_test.cc",
         "prebuilt_tools_test.cc",
         "reference_table_test.cc",
+        "runtime_callbacks_test.cc",
         "thread_pool_test.cc",
         "transaction_test.cc",
         "type_lookup_table_test.cc",
diff --git a/runtime/arch/arm/quick_method_frame_info_arm.h b/runtime/arch/arm/quick_method_frame_info_arm.h
index 4b23c77..35f1948 100644
--- a/runtime/arch/arm/quick_method_frame_info_arm.h
+++ b/runtime/arch/arm/quick_method_frame_info_arm.h
@@ -62,7 +62,7 @@
 
 constexpr uint32_t ArmCalleeSaveFpSpills(Runtime::CalleeSaveType type) {
   return kArmCalleeSaveFpAlwaysSpills | kArmCalleeSaveFpRefSpills |
-      (type == Runtime::kSaveRefsAndArgs ? kArmCalleeSaveFpArgSpills: 0) |
+      (type == Runtime::kSaveRefsAndArgs ? kArmCalleeSaveFpArgSpills : 0) |
       (type == Runtime::kSaveAllCalleeSaves ? kArmCalleeSaveFpAllSpills : 0) |
       (type == Runtime::kSaveEverything ? kArmCalleeSaveFpEverythingSpills : 0);
 }
diff --git a/runtime/arch/arm64/instruction_set_features_arm64.cc b/runtime/arch/arm64/instruction_set_features_arm64.cc
index c598743..01bd177 100644
--- a/runtime/arch/arm64/instruction_set_features_arm64.cc
+++ b/runtime/arch/arm64/instruction_set_features_arm64.cc
@@ -33,7 +33,16 @@
     const std::string& variant, std::string* error_msg) {
   // Look for variants that need a fix for a53 erratum 835769.
   static const char* arm64_variants_with_a53_835769_bug[] = {
-      "default", "generic", "cortex-a53"  // Pessimistically assume all generic ARM64s are A53s.
+      // Pessimistically assume all generic CPUs are cortex-a53.
+      "default",
+      "generic",
+      "cortex-a53",
+      "cortex-a53.a57",
+      "cortex-a53.a72",
+      // Pessimistically assume all "big" cortex CPUs are paired with a cortex-a53.
+      "cortex-a57",
+      "cortex-a72",
+      "cortex-a73",
   };
   bool needs_a53_835769_fix = FindVariantInArray(arm64_variants_with_a53_835769_bug,
                                                  arraysize(arm64_variants_with_a53_835769_bug),
@@ -42,7 +51,10 @@
   if (!needs_a53_835769_fix) {
     // Check to see if this is an expected variant.
     static const char* arm64_known_variants[] = {
-        "denver64", "kryo", "exynos-m1"
+        "cortex-a35",
+        "exynos-m1",
+        "denver64",
+        "kryo"
     };
     if (!FindVariantInArray(arm64_known_variants, arraysize(arm64_known_variants), variant)) {
       std::ostringstream os;
diff --git a/runtime/arch/arm64/instruction_set_features_arm64_test.cc b/runtime/arch/arm64/instruction_set_features_arm64_test.cc
index cefa499..91cb58f 100644
--- a/runtime/arch/arm64/instruction_set_features_arm64_test.cc
+++ b/runtime/arch/arm64/instruction_set_features_arm64_test.cc
@@ -30,6 +30,40 @@
   EXPECT_TRUE(arm64_features->Equals(arm64_features.get()));
   EXPECT_STREQ("a53", arm64_features->GetFeatureString().c_str());
   EXPECT_EQ(arm64_features->AsBitmap(), 1U);
+
+  std::unique_ptr<const InstructionSetFeatures> cortex_a57_features(
+      InstructionSetFeatures::FromVariant(kArm64, "cortex-a57", &error_msg));
+  ASSERT_TRUE(cortex_a57_features.get() != nullptr) << error_msg;
+  EXPECT_EQ(cortex_a57_features->GetInstructionSet(), kArm64);
+  EXPECT_TRUE(cortex_a57_features->Equals(cortex_a57_features.get()));
+  EXPECT_STREQ("a53", cortex_a57_features->GetFeatureString().c_str());
+  EXPECT_EQ(cortex_a57_features->AsBitmap(), 1U);
+
+  std::unique_ptr<const InstructionSetFeatures> cortex_a73_features(
+      InstructionSetFeatures::FromVariant(kArm64, "cortex-a73", &error_msg));
+  ASSERT_TRUE(cortex_a73_features.get() != nullptr) << error_msg;
+  EXPECT_EQ(cortex_a73_features->GetInstructionSet(), kArm64);
+  EXPECT_TRUE(cortex_a73_features->Equals(cortex_a73_features.get()));
+  EXPECT_STREQ("a53", cortex_a73_features->GetFeatureString().c_str());
+  EXPECT_EQ(cortex_a73_features->AsBitmap(), 1U);
+
+  std::unique_ptr<const InstructionSetFeatures> cortex_a35_features(
+      InstructionSetFeatures::FromVariant(kArm64, "cortex-a35", &error_msg));
+  ASSERT_TRUE(cortex_a35_features.get() != nullptr) << error_msg;
+  EXPECT_EQ(cortex_a35_features->GetInstructionSet(), kArm64);
+  EXPECT_TRUE(cortex_a35_features->Equals(cortex_a35_features.get()));
+  EXPECT_STREQ("-a53", cortex_a35_features->GetFeatureString().c_str());
+  EXPECT_EQ(cortex_a35_features->AsBitmap(), 0U);
+
+  std::unique_ptr<const InstructionSetFeatures> kryo_features(
+      InstructionSetFeatures::FromVariant(kArm64, "kryo", &error_msg));
+  ASSERT_TRUE(kryo_features.get() != nullptr) << error_msg;
+  EXPECT_EQ(kryo_features->GetInstructionSet(), kArm64);
+  EXPECT_TRUE(kryo_features->Equals(kryo_features.get()));
+  EXPECT_TRUE(kryo_features->Equals(cortex_a35_features.get()));
+  EXPECT_FALSE(kryo_features->Equals(cortex_a57_features.get()));
+  EXPECT_STREQ("-a53", kryo_features->GetFeatureString().c_str());
+  EXPECT_EQ(kryo_features->AsBitmap(), 0U);
 }
 
 }  // namespace art
diff --git a/runtime/arch/arm64/quick_method_frame_info_arm64.h b/runtime/arch/arm64/quick_method_frame_info_arm64.h
index 36f283b..32d9d08 100644
--- a/runtime/arch/arm64/quick_method_frame_info_arm64.h
+++ b/runtime/arch/arm64/quick_method_frame_info_arm64.h
@@ -85,7 +85,7 @@
 
 constexpr uint32_t Arm64CalleeSaveFpSpills(Runtime::CalleeSaveType type) {
   return kArm64CalleeSaveFpAlwaysSpills | kArm64CalleeSaveFpRefSpills |
-      (type == Runtime::kSaveRefsAndArgs ? kArm64CalleeSaveFpArgSpills: 0) |
+      (type == Runtime::kSaveRefsAndArgs ? kArm64CalleeSaveFpArgSpills : 0) |
       (type == Runtime::kSaveAllCalleeSaves ? kArm64CalleeSaveFpAllSpills : 0) |
       (type == Runtime::kSaveEverything ? kArm64CalleeSaveFpEverythingSpills : 0);
 }
diff --git a/runtime/arch/code_offset.h b/runtime/arch/code_offset.h
new file mode 100644
index 0000000..ab04b1e
--- /dev/null
+++ b/runtime/arch/code_offset.h
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ART_RUNTIME_ARCH_CODE_OFFSET_H_
+#define ART_RUNTIME_ARCH_CODE_OFFSET_H_
+
+#include <iosfwd>
+
+#include "base/bit_utils.h"
+#include "base/logging.h"
+#include "instruction_set.h"
+
+namespace art {
+
+// CodeOffset is a holder for compressed code offsets. Since some architectures have alignment
+// requirements it is possible to compress code offsets to reduce stack map sizes.
+class CodeOffset {
+ public:
+  ALWAYS_INLINE static CodeOffset FromOffset(uint32_t offset, InstructionSet isa = kRuntimeISA) {
+    return CodeOffset(offset / GetInstructionSetInstructionAlignment(isa));
+  }
+
+  ALWAYS_INLINE static CodeOffset FromCompressedOffset(uint32_t offset) {
+    return CodeOffset(offset);
+  }
+
+  ALWAYS_INLINE uint32_t Uint32Value(InstructionSet isa = kRuntimeISA) const {
+    uint32_t decoded = value_ * GetInstructionSetInstructionAlignment(isa);
+    DCHECK_GE(decoded, value_) << "Integer overflow";
+    return decoded;
+  }
+
+  // Return compressed internal value.
+  ALWAYS_INLINE uint32_t CompressedValue() const {
+    return value_;
+  }
+
+  ALWAYS_INLINE CodeOffset() = default;
+  ALWAYS_INLINE CodeOffset(const CodeOffset&) = default;
+  ALWAYS_INLINE CodeOffset& operator=(const CodeOffset&) = default;
+  ALWAYS_INLINE CodeOffset& operator=(CodeOffset&&) = default;
+
+ private:
+  ALWAYS_INLINE explicit CodeOffset(uint32_t value) : value_(value) {}
+
+  uint32_t value_ = 0u;
+};
+
+inline bool operator==(const CodeOffset& a, const CodeOffset& b) {
+  return a.CompressedValue() == b.CompressedValue();
+}
+
+inline bool operator!=(const CodeOffset& a, const CodeOffset& b) {
+  return !(a == b);
+}
+
+inline bool operator<(const CodeOffset& a, const CodeOffset& b) {
+  return a.CompressedValue() < b.CompressedValue();
+}
+
+inline bool operator<=(const CodeOffset& a, const CodeOffset& b) {
+  return a.CompressedValue() <= b.CompressedValue();
+}
+
+inline bool operator>(const CodeOffset& a, const CodeOffset& b) {
+  return a.CompressedValue() > b.CompressedValue();
+}
+
+inline bool operator>=(const CodeOffset& a, const CodeOffset& b) {
+  return a.CompressedValue() >= b.CompressedValue();
+}
+
+inline std::ostream& operator<<(std::ostream& os, const CodeOffset& offset) {
+  return os << offset.Uint32Value();
+}
+
+}  // namespace art
+
+#endif  // ART_RUNTIME_ARCH_CODE_OFFSET_H_
diff --git a/runtime/arch/instruction_set.h b/runtime/arch/instruction_set.h
index 4a8bea4..99aea62 100644
--- a/runtime/arch/instruction_set.h
+++ b/runtime/arch/instruction_set.h
@@ -75,6 +75,14 @@
 // X86 instruction alignment. This is the recommended alignment for maximum performance.
 static constexpr size_t kX86Alignment = 16;
 
+// Different than code alignment since code alignment is only first instruction of method.
+static constexpr size_t kThumb2InstructionAlignment = 2;
+static constexpr size_t kArm64InstructionAlignment = 4;
+static constexpr size_t kX86InstructionAlignment = 1;
+static constexpr size_t kX86_64InstructionAlignment = 1;
+static constexpr size_t kMipsInstructionAlignment = 2;
+static constexpr size_t kMips64InstructionAlignment = 2;
+
 const char* GetInstructionSetString(InstructionSet isa);
 
 // Note: Returns kNone when the string cannot be parsed to a known value.
@@ -106,6 +114,17 @@
   }
 }
 
+ALWAYS_INLINE static inline constexpr size_t GetInstructionSetInstructionAlignment(
+    InstructionSet isa) {
+  return (isa == kThumb2 || isa == kArm) ? kThumb2InstructionAlignment :
+         (isa == kArm64) ? kArm64InstructionAlignment :
+         (isa == kX86) ? kX86InstructionAlignment :
+         (isa == kX86_64) ? kX86_64InstructionAlignment :
+         (isa == kMips) ? kMipsInstructionAlignment :
+         (isa == kMips64) ? kMips64InstructionAlignment :
+         0;  // Invalid case, but constexpr doesn't support asserts.
+}
+
 static inline bool IsValidInstructionSet(InstructionSet isa) {
   switch (isa) {
     case kArm:
diff --git a/runtime/arch/instruction_set_test.cc b/runtime/arch/instruction_set_test.cc
index 5aae93a..b251b57 100644
--- a/runtime/arch/instruction_set_test.cc
+++ b/runtime/arch/instruction_set_test.cc
@@ -44,6 +44,15 @@
   EXPECT_STREQ("none", GetInstructionSetString(kNone));
 }
 
+TEST(InstructionSetTest, GetInstructionSetInstructionAlignment) {
+  EXPECT_EQ(GetInstructionSetInstructionAlignment(kThumb2), kThumb2InstructionAlignment);
+  EXPECT_EQ(GetInstructionSetInstructionAlignment(kArm64), kArm64InstructionAlignment);
+  EXPECT_EQ(GetInstructionSetInstructionAlignment(kX86), kX86InstructionAlignment);
+  EXPECT_EQ(GetInstructionSetInstructionAlignment(kX86_64), kX86_64InstructionAlignment);
+  EXPECT_EQ(GetInstructionSetInstructionAlignment(kMips), kMipsInstructionAlignment);
+  EXPECT_EQ(GetInstructionSetInstructionAlignment(kMips64), kMips64InstructionAlignment);
+}
+
 TEST(InstructionSetTest, TestRoundTrip) {
   EXPECT_EQ(kRuntimeISA, GetInstructionSetFromString(GetInstructionSetString(kRuntimeISA)));
 }
diff --git a/runtime/arch/mips64/quick_method_frame_info_mips64.h b/runtime/arch/mips64/quick_method_frame_info_mips64.h
index 397776e..d774473 100644
--- a/runtime/arch/mips64/quick_method_frame_info_mips64.h
+++ b/runtime/arch/mips64/quick_method_frame_info_mips64.h
@@ -78,7 +78,7 @@
 
 constexpr uint32_t Mips64CalleeSaveFpSpills(Runtime::CalleeSaveType type) {
   return kMips64CalleeSaveFpRefSpills |
-      (type == Runtime::kSaveRefsAndArgs ? kMips64CalleeSaveFpArgSpills: 0) |
+      (type == Runtime::kSaveRefsAndArgs ? kMips64CalleeSaveFpArgSpills : 0) |
       (type == Runtime::kSaveAllCalleeSaves ? kMips64CalleeSaveFpAllSpills : 0) |
       (type == Runtime::kSaveEverything ? kMips64CalleeSaveFpEverythingSpills : 0);
 }
diff --git a/runtime/art_field-inl.h b/runtime/art_field-inl.h
index b9f688d..917db3e 100644
--- a/runtime/art_field-inl.h
+++ b/runtime/art_field-inl.h
@@ -52,7 +52,7 @@
 }
 
 inline MemberOffset ArtField::GetOffset() {
-  DCHECK(GetDeclaringClass()->IsResolved() || GetDeclaringClass()->IsErroneous());
+  DCHECK(GetDeclaringClass()->IsResolved());
   return MemberOffset(offset_);
 }
 
diff --git a/runtime/art_method-inl.h b/runtime/art_method-inl.h
index 15938c5..a35c7ab 100644
--- a/runtime/art_method-inl.h
+++ b/runtime/art_method-inl.h
@@ -109,8 +109,7 @@
 }
 
 inline uint16_t ArtMethod::GetMethodIndex() {
-  DCHECK(IsRuntimeMethod() || GetDeclaringClass()->IsResolved() ||
-         GetDeclaringClass()->IsErroneous());
+  DCHECK(IsRuntimeMethod() || GetDeclaringClass()->IsResolved());
   return method_index_;
 }
 
diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc
index 49cffed..e65672a 100644
--- a/runtime/class_linker.cc
+++ b/runtime/class_linker.cc
@@ -65,7 +65,7 @@
 #include "interpreter/interpreter.h"
 #include "jit/jit.h"
 #include "jit/jit_code_cache.h"
-#include "jit/offline_profiling_info.h"
+#include "jit/profile_compilation_info.h"
 #include "jni_internal.h"
 #include "leb128.h"
 #include "linear_alloc.h"
@@ -96,6 +96,7 @@
 #include "object_lock.h"
 #include "os.h"
 #include "runtime.h"
+#include "runtime_callbacks.h"
 #include "ScopedLocalRef.h"
 #include "scoped_thread_state_change-inl.h"
 #include "thread-inl.h"
@@ -1339,7 +1340,7 @@
           // The image space is not yet added to the heap, avoid read barriers.
           ObjPtr<mirror::Class> klass = types[j].Read();
           if (space->HasAddress(klass.Ptr())) {
-            DCHECK_NE(klass->GetStatus(), mirror::Class::kStatusError);
+            DCHECK(!klass->IsErroneous()) << klass->GetStatus();
             auto it = new_class_set->Find(ClassTable::TableSlot(klass));
             DCHECK(it != new_class_set->end());
             DCHECK_EQ(it->Read(), klass);
@@ -1397,7 +1398,11 @@
         class_loader_(class_loader) {}
 
   bool operator()(ObjPtr<mirror::Class> klass) const REQUIRES_SHARED(Locks::mutator_lock_) {
-    klass->SetClassLoader(class_loader_);
+    // Do not update class loader for boot image classes where the app image
+    // class loader is only the initiating loader but not the defining loader.
+    if (klass->GetClassLoader() != nullptr) {
+      klass->SetClassLoader(class_loader_);
+    }
     return true;
   }
 
@@ -1698,7 +1703,7 @@
       for (int32_t j = 0, num_types = h_dex_cache->NumResolvedTypes(); j < num_types; j++) {
         ObjPtr<mirror::Class> klass = types[j].Read();
         if (klass != nullptr) {
-          DCHECK_NE(klass->GetStatus(), mirror::Class::kStatusError);
+          DCHECK(!klass->IsErroneous()) << klass->GetStatus();
         }
       }
     } else {
@@ -2227,7 +2232,7 @@
   // For temporary classes we must wait for them to be retired.
   if (init_done_ && klass->IsTemp()) {
     CHECK(!klass->IsResolved());
-    if (klass->IsErroneous()) {
+    if (klass->IsErroneousUnresolved()) {
       ThrowEarlierClassFailure(klass);
       return nullptr;
     }
@@ -2235,10 +2240,10 @@
     Handle<mirror::Class> h_class(hs.NewHandle(klass));
     ObjectLock<mirror::Class> lock(self, h_class);
     // Loop and wait for the resolving thread to retire this class.
-    while (!h_class->IsRetired() && !h_class->IsErroneous()) {
+    while (!h_class->IsRetired() && !h_class->IsErroneousUnresolved()) {
       lock.WaitIgnoringInterrupts();
     }
-    if (h_class->IsErroneous()) {
+    if (h_class->IsErroneousUnresolved()) {
       ThrowEarlierClassFailure(h_class.Get());
       return nullptr;
     }
@@ -2253,7 +2258,7 @@
   static const size_t kNumYieldIterations = 1000;
   // How long each sleep is in us.
   static const size_t kSleepDurationUS = 1000;  // 1 ms.
-  while (!klass->IsResolved() && !klass->IsErroneous()) {
+  while (!klass->IsResolved() && !klass->IsErroneousUnresolved()) {
     StackHandleScope<1> hs(self);
     HandleWrapperObjPtr<mirror::Class> h_class(hs.NewHandleWrapper(&klass));
     {
@@ -2264,7 +2269,7 @@
         // Check for circular dependencies between classes, the lock is required for SetStatus.
         if (!h_class->IsResolved() && h_class->GetClinitThreadId() == self->GetTid()) {
           ThrowClassCircularityError(h_class.Get());
-          mirror::Class::SetStatus(h_class, mirror::Class::kStatusError, self);
+          mirror::Class::SetStatus(h_class, mirror::Class::kStatusErrorUnresolved, self);
           return nullptr;
         }
       }
@@ -2281,7 +2286,7 @@
     ++index;
   }
 
-  if (klass->IsErroneous()) {
+  if (klass->IsErroneousUnresolved()) {
     ThrowEarlierClassFailure(klass);
     return nullptr;
   }
@@ -2457,10 +2462,8 @@
     return EnsureResolved(self, descriptor, klass);
   }
   // Class is not yet loaded.
-  if (descriptor[0] == '[') {
-    return CreateArrayClass(self, descriptor, hash, class_loader);
-  } else if (class_loader.Get() == nullptr) {
-    // The boot class loader, search the boot class path.
+  if (descriptor[0] != '[' && class_loader.Get() == nullptr) {
+    // Non-array class and the boot class loader, search the boot class path.
     ClassPathEntry pair = FindInClassPath(descriptor, hash, boot_class_path_);
     if (pair.second != nullptr) {
       return DefineClass(self,
@@ -2473,14 +2476,21 @@
       // The boot class loader is searched ahead of the application class loader, failures are
       // expected and will be wrapped in a ClassNotFoundException. Use the pre-allocated error to
       // trigger the chaining with a proper stack trace.
-      ObjPtr<mirror::Throwable> pre_allocated = Runtime::Current()->GetPreAllocatedNoClassDefFoundError();
+      ObjPtr<mirror::Throwable> pre_allocated =
+          Runtime::Current()->GetPreAllocatedNoClassDefFoundError();
       self->SetException(pre_allocated);
       return nullptr;
     }
+  }
+  ObjPtr<mirror::Class> result_ptr;
+  bool descriptor_equals;
+  if (descriptor[0] == '[') {
+    result_ptr = CreateArrayClass(self, descriptor, hash, class_loader);
+    DCHECK_EQ(result_ptr == nullptr, self->IsExceptionPending());
+    DCHECK(result_ptr == nullptr || result_ptr->DescriptorEquals(descriptor));
+    descriptor_equals = true;
   } else {
     ScopedObjectAccessUnchecked soa(self);
-    ObjPtr<mirror::Class> result_ptr;
-    bool descriptor_equals;
     bool known_hierarchy =
         FindClassInBaseDexClassLoader(soa, self, descriptor, hash, class_loader, &result_ptr);
     if (result_ptr != nullptr) {
@@ -2524,16 +2534,7 @@
                                                  WellKnownClasses::java_lang_ClassLoader_loadClass,
                                                  class_name_object.get()));
       }
-      if (self->IsExceptionPending()) {
-        // If the ClassLoader threw, pass that exception up.
-        // However, to comply with the RI behavior, first check if another thread succeeded.
-        result_ptr = LookupClass(self, descriptor, hash, class_loader.Get());
-        if (result_ptr != nullptr && !result_ptr->IsErroneous()) {
-          self->ClearException();
-          return EnsureResolved(self, descriptor, result_ptr);
-        }
-        return nullptr;
-      } else if (result.get() == nullptr) {
+      if (result.get() == nullptr && !self->IsExceptionPending()) {
         // broken loader - throw NPE to be compatible with Dalvik
         ThrowNullPointerException(StringPrintf("ClassLoader.loadClass returned null for %s",
                                                class_name_string.c_str()).c_str());
@@ -2541,50 +2542,60 @@
       }
       result_ptr = soa.Decode<mirror::Class>(result.get());
       // Check the name of the returned class.
-      descriptor_equals = result_ptr->DescriptorEquals(descriptor);
+      descriptor_equals = (result_ptr != nullptr) && result_ptr->DescriptorEquals(descriptor);
     }
-
-    // Try to insert the class to the class table, checking for mismatch.
-    ObjPtr<mirror::Class> old;
-    {
-      WriterMutexLock mu(self, *Locks::classlinker_classes_lock_);
-      ClassTable* const class_table = InsertClassTableForClassLoader(class_loader.Get());
-      old = class_table->Lookup(descriptor, hash);
-      if (old == nullptr) {
-        old = result_ptr;  // For the comparison below, after releasing the lock.
-        if (descriptor_equals) {
-          class_table->InsertWithHash(result_ptr.Ptr(), hash);
-          Runtime::Current()->GetHeap()->WriteBarrierEveryFieldOf(class_loader.Get());
-        }  // else throw below, after releasing the lock.
-      }
-    }
-    if (UNLIKELY(old != result_ptr)) {
-      // Return `old` (even if `!descriptor_equals`) to mimic the RI behavior for parallel
-      // capable class loaders.  (All class loaders are considered parallel capable on Android.)
-      mirror::Class* loader_class = class_loader->GetClass();
-      const char* loader_class_name =
-          loader_class->GetDexFile().StringByTypeIdx(loader_class->GetDexTypeIndex());
-      LOG(WARNING) << "Initiating class loader of type " << DescriptorToDot(loader_class_name)
-          << " is not well-behaved; it returned a different Class for racing loadClass(\""
-          << DescriptorToDot(descriptor) << "\").";
-      return EnsureResolved(self, descriptor, old);
-    }
-    if (UNLIKELY(!descriptor_equals)) {
-      std::string result_storage;
-      const char* result_name = result_ptr->GetDescriptor(&result_storage);
-      std::string loader_storage;
-      const char* loader_class_name = class_loader->GetClass()->GetDescriptor(&loader_storage);
-      ThrowNoClassDefFoundError(
-          "Initiating class loader of type %s returned class %s instead of %s.",
-          DescriptorToDot(loader_class_name).c_str(),
-          DescriptorToDot(result_name).c_str(),
-          DescriptorToDot(descriptor).c_str());
-      return nullptr;
-    }
-    // success, return mirror::Class*
-    return result_ptr.Ptr();
   }
-  UNREACHABLE();
+
+  if (self->IsExceptionPending()) {
+    // If the ClassLoader threw or array class allocation failed, pass that exception up.
+    // However, to comply with the RI behavior, first check if another thread succeeded.
+    result_ptr = LookupClass(self, descriptor, hash, class_loader.Get());
+    if (result_ptr != nullptr && !result_ptr->IsErroneous()) {
+      self->ClearException();
+      return EnsureResolved(self, descriptor, result_ptr);
+    }
+    return nullptr;
+  }
+
+  // Try to insert the class to the class table, checking for mismatch.
+  ObjPtr<mirror::Class> old;
+  {
+    WriterMutexLock mu(self, *Locks::classlinker_classes_lock_);
+    ClassTable* const class_table = InsertClassTableForClassLoader(class_loader.Get());
+    old = class_table->Lookup(descriptor, hash);
+    if (old == nullptr) {
+      old = result_ptr;  // For the comparison below, after releasing the lock.
+      if (descriptor_equals) {
+        class_table->InsertWithHash(result_ptr.Ptr(), hash);
+        Runtime::Current()->GetHeap()->WriteBarrierEveryFieldOf(class_loader.Get());
+      }  // else throw below, after releasing the lock.
+    }
+  }
+  if (UNLIKELY(old != result_ptr)) {
+    // Return `old` (even if `!descriptor_equals`) to mimic the RI behavior for parallel
+    // capable class loaders.  (All class loaders are considered parallel capable on Android.)
+    mirror::Class* loader_class = class_loader->GetClass();
+    const char* loader_class_name =
+        loader_class->GetDexFile().StringByTypeIdx(loader_class->GetDexTypeIndex());
+    LOG(WARNING) << "Initiating class loader of type " << DescriptorToDot(loader_class_name)
+        << " is not well-behaved; it returned a different Class for racing loadClass(\""
+        << DescriptorToDot(descriptor) << "\").";
+    return EnsureResolved(self, descriptor, old);
+  }
+  if (UNLIKELY(!descriptor_equals)) {
+    std::string result_storage;
+    const char* result_name = result_ptr->GetDescriptor(&result_storage);
+    std::string loader_storage;
+    const char* loader_class_name = class_loader->GetClass()->GetDescriptor(&loader_storage);
+    ThrowNoClassDefFoundError(
+        "Initiating class loader of type %s returned class %s instead of %s.",
+        DescriptorToDot(loader_class_name).c_str(),
+        DescriptorToDot(result_name).c_str(),
+        DescriptorToDot(descriptor).c_str());
+    return nullptr;
+  }
+  // success, return mirror::Class*
+  return result_ptr.Ptr();
 }
 
 mirror::Class* ClassLinker::DefineClass(Thread* self,
@@ -2625,13 +2636,26 @@
     self->AssertPendingOOMException();
     return nullptr;
   }
-  ObjPtr<mirror::DexCache> dex_cache = RegisterDexFile(dex_file, class_loader.Get());
+  // Get the real dex file. This will return the input if there aren't any callbacks or they do
+  // nothing.
+  DexFile const* new_dex_file = nullptr;
+  DexFile::ClassDef const* new_class_def = nullptr;
+  // TODO We should ideally figure out some way to move this after we get a lock on the klass so it
+  // will only be called once.
+  Runtime::Current()->GetRuntimeCallbacks()->ClassPreDefine(descriptor,
+                                                            klass,
+                                                            class_loader,
+                                                            dex_file,
+                                                            dex_class_def,
+                                                            &new_dex_file,
+                                                            &new_class_def);
+  ObjPtr<mirror::DexCache> dex_cache = RegisterDexFile(*new_dex_file, class_loader.Get());
   if (dex_cache == nullptr) {
     self->AssertPendingOOMException();
     return nullptr;
   }
   klass->SetDexCache(dex_cache);
-  SetupClass(dex_file, dex_class_def, klass, class_loader.Get());
+  SetupClass(*new_dex_file, *new_class_def, klass, class_loader.Get());
 
   // Mark the string class by setting its access flag.
   if (UNLIKELY(!init_done_)) {
@@ -2657,27 +2681,32 @@
   // end up allocating unfree-able linear alloc resources and then lose the race condition. The
   // other reason is that the field roots are only visited from the class table. So we need to be
   // inserted before we allocate / fill in these fields.
-  LoadClass(self, dex_file, dex_class_def, klass);
+  LoadClass(self, *new_dex_file, *new_class_def, klass);
   if (self->IsExceptionPending()) {
     VLOG(class_linker) << self->GetException()->Dump();
     // An exception occured during load, set status to erroneous while holding klass' lock in case
     // notification is necessary.
     if (!klass->IsErroneous()) {
-      mirror::Class::SetStatus(klass, mirror::Class::kStatusError, self);
+      mirror::Class::SetStatus(klass, mirror::Class::kStatusErrorUnresolved, self);
     }
     return nullptr;
   }
 
   // Finish loading (if necessary) by finding parents
   CHECK(!klass->IsLoaded());
-  if (!LoadSuperAndInterfaces(klass, dex_file)) {
+  if (!LoadSuperAndInterfaces(klass, *new_dex_file)) {
     // Loading failed.
     if (!klass->IsErroneous()) {
-      mirror::Class::SetStatus(klass, mirror::Class::kStatusError, self);
+      mirror::Class::SetStatus(klass, mirror::Class::kStatusErrorUnresolved, self);
     }
     return nullptr;
   }
   CHECK(klass->IsLoaded());
+
+  // At this point the class is loaded. Publish a ClassLoad even.
+  // Note: this may be a temporary class. It is a listener's responsibility to handle this.
+  Runtime::Current()->GetRuntimeCallbacks()->ClassLoad(klass);
+
   // Link the class (if necessary)
   CHECK(!klass->IsResolved());
   // TODO: Use fast jobjects?
@@ -2687,13 +2716,13 @@
   if (!LinkClass(self, descriptor, klass, interfaces, &h_new_class)) {
     // Linking failed.
     if (!klass->IsErroneous()) {
-      mirror::Class::SetStatus(klass, mirror::Class::kStatusError, self);
+      mirror::Class::SetStatus(klass, mirror::Class::kStatusErrorUnresolved, self);
     }
     return nullptr;
   }
   self->AssertNoPendingException();
   CHECK(h_new_class.Get() != nullptr) << descriptor;
-  CHECK(h_new_class->IsResolved()) << descriptor;
+  CHECK(h_new_class->IsResolved() && !h_new_class->IsErroneousResolved()) << descriptor;
 
   // Instrumentation may have updated entrypoints for all methods of all
   // classes. However it could not update methods of this class while we
@@ -2718,7 +2747,7 @@
    * The class has been prepared and resolved but possibly not yet verified
    * at this point.
    */
-  Dbg::PostClassPrepare(h_new_class.Get());
+  Runtime::Current()->GetRuntimeCallbacks()->ClassPrepare(klass, h_new_class);
 
   // Notify native debugger of the new class and its layout.
   jit::Jit::NewTypeLoadedIfUsingJit(h_new_class.Get());
@@ -3488,7 +3517,8 @@
   // class to the hash table --- necessary because of possible races with
   // other threads.)
   if (class_loader.Get() != component_type->GetClassLoader()) {
-    ObjPtr<mirror::Class> new_class = LookupClass(self, descriptor, hash, component_type->GetClassLoader());
+    ObjPtr<mirror::Class> new_class =
+        LookupClass(self, descriptor, hash, component_type->GetClassLoader());
     if (new_class != nullptr) {
       return new_class.Ptr();
     }
@@ -3787,7 +3817,7 @@
   }
   // Need to grab the lock to change status.
   ObjectLock<mirror::Class> super_lock(self, klass);
-  mirror::Class::SetStatus(klass, mirror::Class::kStatusError, self);
+  mirror::Class::SetStatus(klass, mirror::Class::kStatusErrorResolved, self);
   return false;
 }
 
@@ -3909,8 +3939,8 @@
   bool preverified = VerifyClassUsingOatFile(dex_file, klass.Get(), oat_file_class_status);
   // If the oat file says the class had an error, re-run the verifier. That way we will get a
   // precise error message. To ensure a rerun, test:
-  //     oat_file_class_status == mirror::Class::kStatusError => !preverified
-  DCHECK(!(oat_file_class_status == mirror::Class::kStatusError) || !preverified);
+  //     mirror::Class::IsErroneous(oat_file_class_status) => !preverified
+  DCHECK(!mirror::Class::IsErroneous(oat_file_class_status) || !preverified);
 
   std::string error_msg;
   verifier::MethodVerifier::FailureKind verifier_failure = verifier::MethodVerifier::kNoFailure;
@@ -3968,7 +3998,7 @@
                   << " because: " << error_msg;
     self->AssertNoPendingException();
     ThrowVerifyError(klass.Get(), "%s", error_msg.c_str());
-    mirror::Class::SetStatus(klass, mirror::Class::kStatusError, self);
+    mirror::Class::SetStatus(klass, mirror::Class::kStatusErrorResolved, self);
   }
   if (preverified || verifier_failure == verifier::MethodVerifier::kNoFailure) {
     // Class is verified so we don't need to do any access check on its methods.
@@ -4059,7 +4089,7 @@
     // at compile time).
     return false;
   }
-  if (oat_file_class_status == mirror::Class::kStatusError) {
+  if (mirror::Class::IsErroneous(oat_file_class_status)) {
     // Compile time verification failed with a hard error. This is caused by invalid instructions
     // in the class. These errors are unrecoverable.
     return false;
@@ -4218,7 +4248,7 @@
     Handle<mirror::ObjectArray<mirror::Class>> h_interfaces(
         hs.NewHandle(soa.Decode<mirror::ObjectArray<mirror::Class>>(interfaces)));
     if (!LinkClass(self, descriptor.c_str(), klass, h_interfaces, &new_class)) {
-      mirror::Class::SetStatus(klass, mirror::Class::kStatusError, self);
+      mirror::Class::SetStatus(klass, mirror::Class::kStatusErrorUnresolved, self);
       return nullptr;
     }
   }
@@ -4433,7 +4463,8 @@
       return false;
     }
 
-    CHECK(klass->IsResolved()) << klass->PrettyClass() << ": state=" << klass->GetStatus();
+    CHECK(klass->IsResolved() && !klass->IsErroneousResolved())
+        << klass->PrettyClass() << ": state=" << klass->GetStatus();
 
     if (!klass->IsVerified()) {
       VerifyClass(self, klass);
@@ -4468,7 +4499,7 @@
       // A separate thread could have moved us all the way to initialized. A "simple" example
       // involves a subclass of the current class being initialized at the same time (which
       // will implicitly initialize the superclass, if scheduled that way). b/28254258
-      DCHECK_NE(mirror::Class::kStatusError, klass->GetStatus());
+      DCHECK(!klass->IsErroneous()) << klass->GetStatus();
       if (klass->IsInitialized()) {
         return true;
       }
@@ -4495,7 +4526,7 @@
     }
 
     if (!ValidateSuperClassDescriptors(klass)) {
-      mirror::Class::SetStatus(klass, mirror::Class::kStatusError, self);
+      mirror::Class::SetStatus(klass, mirror::Class::kStatusErrorResolved, self);
       return false;
     }
     self->AllowThreadSuspension();
@@ -4531,7 +4562,7 @@
             << (self->GetException() != nullptr ? self->GetException()->Dump() : "");
         ObjectLock<mirror::Class> lock(self, klass);
         // Initialization failed because the super-class is erroneous.
-        mirror::Class::SetStatus(klass, mirror::Class::kStatusError, self);
+        mirror::Class::SetStatus(klass, mirror::Class::kStatusErrorResolved, self);
         return false;
       }
     }
@@ -4562,7 +4593,7 @@
         if (!iface_initialized) {
           ObjectLock<mirror::Class> lock(self, klass);
           // Initialization failed because one of our interfaces with default methods is erroneous.
-          mirror::Class::SetStatus(klass, mirror::Class::kStatusError, self);
+          mirror::Class::SetStatus(klass, mirror::Class::kStatusErrorResolved, self);
           return false;
         }
       }
@@ -4635,7 +4666,7 @@
 
     if (self->IsExceptionPending()) {
       WrapExceptionInInitializer(klass);
-      mirror::Class::SetStatus(klass, mirror::Class::kStatusError, self);
+      mirror::Class::SetStatus(klass, mirror::Class::kStatusErrorResolved, self);
       success = false;
     } else if (Runtime::Current()->IsTransactionAborted()) {
       // The exception thrown when the transaction aborted has been caught and cleared
@@ -4644,7 +4675,7 @@
                      << mirror::Class::PrettyDescriptor(klass.Get())
                      << " without exception while transaction was aborted: re-throw it now.";
       Runtime::Current()->ThrowTransactionAbortError(self);
-      mirror::Class::SetStatus(klass, mirror::Class::kStatusError, self);
+      mirror::Class::SetStatus(klass, mirror::Class::kStatusErrorResolved, self);
       success = false;
     } else {
       RuntimeStats* global_stats = Runtime::Current()->GetStats();
@@ -4728,7 +4759,7 @@
     // we were not using WaitIgnoringInterrupts), bail out.
     if (self->IsExceptionPending()) {
       WrapExceptionInInitializer(klass);
-      mirror::Class::SetStatus(klass, mirror::Class::kStatusError, self);
+      mirror::Class::SetStatus(klass, mirror::Class::kStatusErrorResolved, self);
       return false;
     }
     // Spurious wakeup? Go back to waiting.
@@ -5139,7 +5170,7 @@
     klass->SetIFieldsPtrUnchecked(nullptr);
     if (UNLIKELY(h_new_class.Get() == nullptr)) {
       self->AssertPendingOOMException();
-      mirror::Class::SetStatus(klass, mirror::Class::kStatusError, self);
+      mirror::Class::SetStatus(klass, mirror::Class::kStatusErrorUnresolved, self);
       return false;
     }
 
@@ -7706,7 +7737,7 @@
       type = LookupClass(self, descriptor, hash, class_loader.Ptr());
     }
   }
-  if (type != nullptr || type->IsResolved()) {
+  if (type != nullptr && type->IsResolved()) {
     return type.Ptr();
   }
   return nullptr;
@@ -7751,7 +7782,7 @@
       }
     }
   }
-  DCHECK((resolved == nullptr) || resolved->IsResolved() || resolved->IsErroneous())
+  DCHECK((resolved == nullptr) || resolved->IsResolved())
       << resolved->PrettyDescriptor() << " " << resolved->GetStatus();
   return resolved.Ptr();
 }
@@ -8486,13 +8517,12 @@
       }
       ++num_resolved;
       DCHECK(!klass->IsProxyClass());
-      if (!klass->IsResolved()) {
-        DCHECK(klass->IsErroneous());
+      DCHECK(klass->IsResolved());
+      if (klass->IsErroneousResolved()) {
         continue;
       }
       ObjPtr<mirror::DexCache> klass_dex_cache = klass->GetDexCache();
       if (klass_dex_cache == dex_cache) {
-        DCHECK(klass->IsResolved());
         CHECK_LT(klass->GetDexClassDefIndex(), num_class_defs);
         class_set.insert(klass->GetDexTypeIndex());
       }
diff --git a/runtime/class_linker.h b/runtime/class_linker.h
index 9b98671..5042fb7 100644
--- a/runtime/class_linker.h
+++ b/runtime/class_linker.h
@@ -34,6 +34,7 @@
 #include "dex_file.h"
 #include "dex_file_types.h"
 #include "gc_root.h"
+#include "handle.h"
 #include "jni.h"
 #include "mirror/class.h"
 #include "object_callbacks.h"
@@ -1194,6 +1195,38 @@
   DISALLOW_COPY_AND_ASSIGN(ClassLinker);
 };
 
+class ClassLoadCallback {
+ public:
+  virtual ~ClassLoadCallback() {}
+
+  // If set we will replace initial_class_def & initial_dex_file with the final versions. The
+  // callback author is responsible for ensuring these are allocated in such a way they can be
+  // cleaned up if another transformation occurs. Note that both must be set or null/unchanged on
+  // return.
+  // Note: the class may be temporary, in which case a following ClassPrepare event will be a
+  //       different object. It is the listener's responsibility to handle this.
+  // Note: This callback is rarely useful so a default implementation has been given that does
+  //       nothing.
+  virtual void ClassPreDefine(const char* descriptor ATTRIBUTE_UNUSED,
+                              Handle<mirror::Class> klass ATTRIBUTE_UNUSED,
+                              Handle<mirror::ClassLoader> class_loader ATTRIBUTE_UNUSED,
+                              const DexFile& initial_dex_file ATTRIBUTE_UNUSED,
+                              const DexFile::ClassDef& initial_class_def ATTRIBUTE_UNUSED,
+                              /*out*/DexFile const** final_dex_file ATTRIBUTE_UNUSED,
+                              /*out*/DexFile::ClassDef const** final_class_def ATTRIBUTE_UNUSED)
+      REQUIRES_SHARED(Locks::mutator_lock_) {}
+
+  // A class has been loaded.
+  // Note: the class may be temporary, in which case a following ClassPrepare event will be a
+  //       different object. It is the listener's responsibility to handle this.
+  virtual void ClassLoad(Handle<mirror::Class> klass) REQUIRES_SHARED(Locks::mutator_lock_) = 0;
+
+  // A class has been prepared, i.e., resolved. As the ClassLoad event might have been for a
+  // temporary class, provide both the former and the current class.
+  virtual void ClassPrepare(Handle<mirror::Class> temp_klass,
+                            Handle<mirror::Class> klass) REQUIRES_SHARED(Locks::mutator_lock_) = 0;
+};
+
 }  // namespace art
 
 #endif  // ART_RUNTIME_CLASS_LINKER_H_
diff --git a/runtime/class_linker_test.cc b/runtime/class_linker_test.cc
index 0341c64..026afdc 100644
--- a/runtime/class_linker_test.cc
+++ b/runtime/class_linker_test.cc
@@ -87,6 +87,7 @@
     EXPECT_FALSE(primitive->IsErroneous());
     EXPECT_TRUE(primitive->IsLoaded());
     EXPECT_TRUE(primitive->IsResolved());
+    EXPECT_FALSE(primitive->IsErroneousResolved());
     EXPECT_TRUE(primitive->IsVerified());
     EXPECT_TRUE(primitive->IsInitialized());
     EXPECT_FALSE(primitive->IsArrayInstance());
@@ -125,6 +126,7 @@
     EXPECT_FALSE(JavaLangObject->IsErroneous());
     EXPECT_TRUE(JavaLangObject->IsLoaded());
     EXPECT_TRUE(JavaLangObject->IsResolved());
+    EXPECT_FALSE(JavaLangObject->IsErroneousResolved());
     EXPECT_TRUE(JavaLangObject->IsVerified());
     EXPECT_TRUE(JavaLangObject->IsInitialized());
     EXPECT_FALSE(JavaLangObject->IsArrayInstance());
@@ -199,6 +201,7 @@
     EXPECT_FALSE(array->IsErroneous());
     EXPECT_TRUE(array->IsLoaded());
     EXPECT_TRUE(array->IsResolved());
+    EXPECT_FALSE(array->IsErroneousResolved());
     EXPECT_TRUE(array->IsVerified());
     EXPECT_TRUE(array->IsInitialized());
     EXPECT_FALSE(array->IsArrayInstance());
@@ -270,6 +273,7 @@
     EXPECT_TRUE(klass->GetDexCache() != nullptr);
     EXPECT_TRUE(klass->IsLoaded());
     EXPECT_TRUE(klass->IsResolved());
+    EXPECT_FALSE(klass->IsErroneousResolved());
     EXPECT_FALSE(klass->IsErroneous());
     EXPECT_FALSE(klass->IsArrayClass());
     EXPECT_TRUE(klass->GetComponentType() == nullptr);
@@ -487,7 +491,7 @@
       // says AccessibleObject is 9 bytes but sizeof(AccessibleObject) is 12 bytes due to padding.
       // The RoundUp is to get around this case.
       static constexpr size_t kPackAlignment = 4;
-      size_t expected_size = RoundUp(is_static ? klass->GetClassSize(): klass->GetObjectSize(),
+      size_t expected_size = RoundUp(is_static ? klass->GetClassSize() : klass->GetObjectSize(),
           kPackAlignment);
       if (sizeof(T) != expected_size) {
         LOG(ERROR) << "Class size mismatch:"
@@ -612,7 +616,7 @@
   ClassExtOffsets() : CheckOffsets<mirror::ClassExt>(false, "Ldalvik/system/ClassExt;") {
     addOffset(OFFSETOF_MEMBER(mirror::ClassExt, obsolete_dex_caches_), "obsoleteDexCaches");
     addOffset(OFFSETOF_MEMBER(mirror::ClassExt, obsolete_methods_), "obsoleteMethods");
-    addOffset(OFFSETOF_MEMBER(mirror::ClassExt, original_dex_cache_), "originalDexCache");
+    addOffset(OFFSETOF_MEMBER(mirror::ClassExt, original_dex_file_bytes_), "originalDexFile");
     addOffset(OFFSETOF_MEMBER(mirror::ClassExt, verify_error_), "verifyError");
   }
 };
@@ -857,6 +861,7 @@
   EXPECT_FALSE(MyClass->IsErroneous());
   EXPECT_TRUE(MyClass->IsLoaded());
   EXPECT_TRUE(MyClass->IsResolved());
+  EXPECT_FALSE(MyClass->IsErroneousResolved());
   EXPECT_FALSE(MyClass->IsVerified());
   EXPECT_FALSE(MyClass->IsInitialized());
   EXPECT_FALSE(MyClass->IsArrayInstance());
@@ -906,6 +911,82 @@
       klass);
 }
 
+TEST_F(ClassLinkerTest, LookupResolvedTypeArray) {
+  ScopedObjectAccess soa(Thread::Current());
+  StackHandleScope<2> hs(soa.Self());
+  Handle<mirror::ClassLoader> class_loader(
+      hs.NewHandle(soa.Decode<mirror::ClassLoader>(LoadDex("AllFields"))));
+  // Get the AllFields class for the dex cache and dex file.
+  ObjPtr<mirror::Class> all_fields_klass
+      = class_linker_->FindClass(soa.Self(), "LAllFields;", class_loader);
+  ASSERT_OBJ_PTR_NE(all_fields_klass, ObjPtr<mirror::Class>(nullptr));
+  Handle<mirror::DexCache> dex_cache = hs.NewHandle(all_fields_klass->GetDexCache());
+  const DexFile& dex_file = *dex_cache->GetDexFile();
+  // Get the index of the array class we want to test.
+  const DexFile::TypeId* array_id = dex_file.FindTypeId("[Ljava/lang/Object;");
+  ASSERT_TRUE(array_id != nullptr);
+  dex::TypeIndex array_idx = dex_file.GetIndexForTypeId(*array_id);
+  // Check that the array class wasn't resolved yet.
+  EXPECT_OBJ_PTR_EQ(
+      class_linker_->LookupResolvedType(dex_file, array_idx, dex_cache.Get(), class_loader.Get()),
+      ObjPtr<mirror::Class>(nullptr));
+  // Resolve the array class we want to test.
+  ObjPtr<mirror::Class> array_klass
+      = class_linker_->FindClass(soa.Self(), "[Ljava/lang/Object;", class_loader);
+  ASSERT_OBJ_PTR_NE(array_klass, ObjPtr<mirror::Class>(nullptr));
+  // Test that LookupResolvedType() finds the array class.
+  EXPECT_OBJ_PTR_EQ(
+      class_linker_->LookupResolvedType(dex_file, array_idx, dex_cache.Get(), class_loader.Get()),
+      array_klass);
+  // Zero out the resolved type and make sure LookupResolvedType() still finds it.
+  dex_cache->SetResolvedType(array_idx, nullptr);
+  EXPECT_TRUE(dex_cache->GetResolvedType(array_idx) == nullptr);
+  EXPECT_OBJ_PTR_EQ(
+      class_linker_->LookupResolvedType(dex_file, array_idx, dex_cache.Get(), class_loader.Get()),
+      array_klass);
+}
+
+TEST_F(ClassLinkerTest, LookupResolvedTypeErroneousInit) {
+  ScopedObjectAccess soa(Thread::Current());
+  StackHandleScope<3> hs(soa.Self());
+  Handle<mirror::ClassLoader> class_loader(
+      hs.NewHandle(soa.Decode<mirror::ClassLoader>(LoadDex("ErroneousInit"))));
+  AssertNonExistentClass("LErroneousInit;");
+  Handle<mirror::Class> klass =
+      hs.NewHandle(class_linker_->FindClass(soa.Self(), "LErroneousInit;", class_loader));
+  ASSERT_OBJ_PTR_NE(klass.Get(), ObjPtr<mirror::Class>(nullptr));
+  dex::TypeIndex type_idx = klass->GetClassDef()->class_idx_;
+  Handle<mirror::DexCache> dex_cache = hs.NewHandle(klass->GetDexCache());
+  const DexFile& dex_file = klass->GetDexFile();
+  EXPECT_OBJ_PTR_EQ(
+      class_linker_->LookupResolvedType(dex_file, type_idx, dex_cache.Get(), class_loader.Get()),
+      klass.Get());
+  // Zero out the resolved type and make sure LookupResolvedType still finds it.
+  dex_cache->SetResolvedType(type_idx, nullptr);
+  EXPECT_TRUE(dex_cache->GetResolvedType(type_idx) == nullptr);
+  EXPECT_OBJ_PTR_EQ(
+      class_linker_->LookupResolvedType(dex_file, type_idx, dex_cache.Get(), class_loader.Get()),
+      klass.Get());
+  // Force initialization to turn the class erroneous.
+  bool initialized = class_linker_->EnsureInitialized(soa.Self(),
+                                                      klass,
+                                                      /* can_init_fields */ true,
+                                                      /* can_init_parents */ true);
+  EXPECT_FALSE(initialized);
+  EXPECT_TRUE(soa.Self()->IsExceptionPending());
+  soa.Self()->ClearException();
+  // Check that the LookupResolvedType() can still find the resolved type.
+  EXPECT_OBJ_PTR_EQ(
+      class_linker_->LookupResolvedType(dex_file, type_idx, dex_cache.Get(), class_loader.Get()),
+      klass.Get());
+  // Zero out the resolved type and make sure LookupResolvedType() still finds it.
+  dex_cache->SetResolvedType(type_idx, nullptr);
+  EXPECT_TRUE(dex_cache->GetResolvedType(type_idx) == nullptr);
+  EXPECT_OBJ_PTR_EQ(
+      class_linker_->LookupResolvedType(dex_file, type_idx, dex_cache.Get(), class_loader.Get()),
+      klass.Get());
+}
+
 TEST_F(ClassLinkerTest, LibCore) {
   ScopedObjectAccess soa(Thread::Current());
   ASSERT_TRUE(java_lang_dex_file_ != nullptr);
diff --git a/runtime/class_table.cc b/runtime/class_table.cc
index 0f985c6..ff846a7 100644
--- a/runtime/class_table.cc
+++ b/runtime/class_table.cc
@@ -129,6 +129,19 @@
   classes_.back().InsertWithHash(TableSlot(klass, hash), hash);
 }
 
+void ClassTable::CopyWithoutLocks(const ClassTable& source_table) {
+  if (kIsDebugBuild) {
+    for (ClassSet& class_set : classes_) {
+      CHECK(class_set.Empty());
+    }
+  }
+  for (const ClassSet& class_set : source_table.classes_) {
+    for (const TableSlot& slot : class_set) {
+      classes_.back().Insert(slot);
+    }
+  }
+}
+
 void ClassTable::InsertWithoutLocks(ObjPtr<mirror::Class> klass) {
   const uint32_t hash = TableSlot::HashDescriptor(klass);
   classes_.back().InsertWithHash(TableSlot(klass, hash), hash);
diff --git a/runtime/class_table.h b/runtime/class_table.h
index f27d809..c8ec28e 100644
--- a/runtime/class_table.h
+++ b/runtime/class_table.h
@@ -240,6 +240,7 @@
   }
 
  private:
+  void CopyWithoutLocks(const ClassTable& source_table) NO_THREAD_SAFETY_ANALYSIS;
   void InsertWithoutLocks(ObjPtr<mirror::Class> klass) NO_THREAD_SAFETY_ANALYSIS;
 
   size_t CountDefiningLoaderClasses(ObjPtr<mirror::ClassLoader> defining_loader,
diff --git a/runtime/common_runtime_test.cc b/runtime/common_runtime_test.cc
index 743fcc8..fc82264 100644
--- a/runtime/common_runtime_test.cc
+++ b/runtime/common_runtime_test.cc
@@ -133,7 +133,9 @@
 
 static bool unstarted_initialized_ = false;
 
-CommonRuntimeTestImpl::CommonRuntimeTestImpl() {}
+CommonRuntimeTestImpl::CommonRuntimeTestImpl()
+    : class_linker_(nullptr), java_lang_dex_file_(nullptr) {
+}
 
 CommonRuntimeTestImpl::~CommonRuntimeTestImpl() {
   // Ensure the dex files are cleaned up before the runtime.
@@ -425,7 +427,9 @@
   TearDownAndroidData(android_data_, true);
   dalvik_cache_.clear();
 
-  Runtime::Current()->GetHeap()->VerifyHeap();  // Check for heap corruption after the test
+  if (runtime_ != nullptr) {
+    runtime_->GetHeap()->VerifyHeap();  // Check for heap corruption after the test
+  }
 }
 
 static std::string GetDexFileName(const std::string& jar_prefix, bool host) {
diff --git a/runtime/debugger.cc b/runtime/debugger.cc
index 006476b..22a3163 100644
--- a/runtime/debugger.cc
+++ b/runtime/debugger.cc
@@ -320,6 +320,9 @@
 size_t Dbg::exception_catch_event_ref_count_ = 0;
 uint32_t Dbg::instrumentation_events_ = 0;
 
+Dbg::DbgThreadLifecycleCallback Dbg::thread_lifecycle_callback_;
+Dbg::DbgClassLoadCallback Dbg::class_load_callback_;
+
 // Breakpoints.
 static std::vector<Breakpoint> gBreakpoints GUARDED_BY(Locks::breakpoint_lock_);
 
@@ -5135,4 +5138,20 @@
   }
 }
 
+void Dbg::DbgThreadLifecycleCallback::ThreadStart(Thread* self) {
+  Dbg::PostThreadStart(self);
+}
+
+void Dbg::DbgThreadLifecycleCallback::ThreadDeath(Thread* self) {
+  Dbg::PostThreadDeath(self);
+}
+
+void Dbg::DbgClassLoadCallback::ClassLoad(Handle<mirror::Class> klass ATTRIBUTE_UNUSED) {
+  // Ignore ClassLoad;
+}
+void Dbg::DbgClassLoadCallback::ClassPrepare(Handle<mirror::Class> temp_klass ATTRIBUTE_UNUSED,
+                                             Handle<mirror::Class> klass) {
+  Dbg::PostClassPrepare(klass.Get());
+}
+
 }  // namespace art
diff --git a/runtime/debugger.h b/runtime/debugger.h
index 3b4a5e1..a7fd160 100644
--- a/runtime/debugger.h
+++ b/runtime/debugger.h
@@ -28,6 +28,8 @@
 #include <vector>
 
 #include "gc_root.h"
+#include "class_linker.h"
+#include "handle.h"
 #include "jdwp/jdwp.h"
 #include "jni.h"
 #include "jvalue.h"
@@ -502,12 +504,6 @@
       REQUIRES_SHARED(Locks::mutator_lock_);
   static void PostException(mirror::Throwable* exception)
       REQUIRES_SHARED(Locks::mutator_lock_);
-  static void PostThreadStart(Thread* t)
-      REQUIRES_SHARED(Locks::mutator_lock_);
-  static void PostThreadDeath(Thread* t)
-      REQUIRES_SHARED(Locks::mutator_lock_);
-  static void PostClassPrepare(mirror::Class* c)
-      REQUIRES_SHARED(Locks::mutator_lock_);
 
   static void UpdateDebugger(Thread* thread, mirror::Object* this_object,
                              ArtMethod* method, uint32_t new_dex_pc,
@@ -707,6 +703,13 @@
     return instrumentation_events_;
   }
 
+  static ThreadLifecycleCallback* GetThreadLifecycleCallback() {
+    return &thread_lifecycle_callback_;
+  }
+  static ClassLoadCallback* GetClassLoadCallback() {
+    return &class_load_callback_;
+  }
+
  private:
   static void ExecuteMethodWithoutPendingException(ScopedObjectAccess& soa, DebugInvokeReq* pReq)
       REQUIRES_SHARED(Locks::mutator_lock_);
@@ -725,9 +728,17 @@
       REQUIRES(!Locks::thread_list_lock_) REQUIRES_SHARED(Locks::mutator_lock_);
 
   static void DdmBroadcast(bool connect) REQUIRES_SHARED(Locks::mutator_lock_);
+
+  static void PostThreadStart(Thread* t)
+      REQUIRES_SHARED(Locks::mutator_lock_);
+  static void PostThreadDeath(Thread* t)
+      REQUIRES_SHARED(Locks::mutator_lock_);
   static void PostThreadStartOrStop(Thread*, uint32_t)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
+  static void PostClassPrepare(mirror::Class* c)
+      REQUIRES_SHARED(Locks::mutator_lock_);
+
   static void PostLocationEvent(ArtMethod* method, int pcOffset,
                                 mirror::Object* thisPtr, int eventFlags,
                                 const JValue* return_value)
@@ -789,6 +800,22 @@
   static size_t exception_catch_event_ref_count_ GUARDED_BY(Locks::deoptimization_lock_);
   static uint32_t instrumentation_events_ GUARDED_BY(Locks::mutator_lock_);
 
+  class DbgThreadLifecycleCallback : public ThreadLifecycleCallback {
+   public:
+    void ThreadStart(Thread* self) OVERRIDE REQUIRES_SHARED(Locks::mutator_lock_);
+    void ThreadDeath(Thread* self) OVERRIDE REQUIRES_SHARED(Locks::mutator_lock_);
+  };
+
+  class DbgClassLoadCallback : public ClassLoadCallback {
+   public:
+    void ClassLoad(Handle<mirror::Class> klass) OVERRIDE REQUIRES_SHARED(Locks::mutator_lock_);
+    void ClassPrepare(Handle<mirror::Class> temp_klass,
+                      Handle<mirror::Class> klass) OVERRIDE REQUIRES_SHARED(Locks::mutator_lock_);
+  };
+
+  static DbgThreadLifecycleCallback thread_lifecycle_callback_;
+  static DbgClassLoadCallback class_load_callback_;
+
   DISALLOW_COPY_AND_ASSIGN(Dbg);
 };
 
diff --git a/runtime/dex2oat_environment_test.h b/runtime/dex2oat_environment_test.h
index b0c4597..7ae9f03 100644
--- a/runtime/dex2oat_environment_test.h
+++ b/runtime/dex2oat_environment_test.h
@@ -160,7 +160,7 @@
   // image at GetImageLocation(). This is used for testing mismatched
   // image checksums in the oat_file_assistant_tests.
   std::string GetImageLocation2() const {
-    return GetImageDirectory() + "/core-npic.art";
+    return GetImageDirectory() + "/core-interpreter.art";
   }
 
   std::string GetDexSrc1() const {
diff --git a/runtime/dexopt_test.cc b/runtime/dexopt_test.cc
new file mode 100644
index 0000000..69c6151
--- /dev/null
+++ b/runtime/dexopt_test.cc
@@ -0,0 +1,236 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <string>
+#include <vector>
+
+#include <backtrace/BacktraceMap.h>
+#include <gtest/gtest.h>
+
+#include "common_runtime_test.h"
+#include "compiler_callbacks.h"
+#include "dex2oat_environment_test.h"
+#include "dexopt_test.h"
+#include "gc/space/image_space.h"
+#include "mem_map.h"
+
+namespace art {
+void DexoptTest::SetUp() {
+  ReserveImageSpace();
+  Dex2oatEnvironmentTest::SetUp();
+}
+
+void DexoptTest::PreRuntimeCreate() {
+  std::string error_msg;
+  ASSERT_TRUE(PreRelocateImage(GetImageLocation(), &error_msg)) << error_msg;
+  ASSERT_TRUE(PreRelocateImage(GetImageLocation2(), &error_msg)) << error_msg;
+  UnreserveImageSpace();
+}
+
+void DexoptTest::PostRuntimeCreate() {
+  ReserveImageSpace();
+}
+
+void DexoptTest::GenerateOatForTest(const std::string& dex_location,
+                        const std::string& oat_location,
+                        CompilerFilter::Filter filter,
+                        bool relocate,
+                        bool pic,
+                        bool with_alternate_image) {
+  std::string dalvik_cache = GetDalvikCache(GetInstructionSetString(kRuntimeISA));
+  std::string dalvik_cache_tmp = dalvik_cache + ".redirected";
+
+  if (!relocate) {
+    // Temporarily redirect the dalvik cache so dex2oat doesn't find the
+    // relocated image file.
+    ASSERT_EQ(0, rename(dalvik_cache.c_str(), dalvik_cache_tmp.c_str())) << strerror(errno);
+  }
+
+  std::vector<std::string> args;
+  args.push_back("--dex-file=" + dex_location);
+  args.push_back("--oat-file=" + oat_location);
+  args.push_back("--compiler-filter=" + CompilerFilter::NameOfFilter(filter));
+  args.push_back("--runtime-arg");
+
+  // Use -Xnorelocate regardless of the relocate argument.
+  // We control relocation by redirecting the dalvik cache when needed
+  // rather than use this flag.
+  args.push_back("-Xnorelocate");
+
+  if (pic) {
+    args.push_back("--compile-pic");
+  }
+
+  std::string image_location = GetImageLocation();
+  if (with_alternate_image) {
+    args.push_back("--boot-image=" + GetImageLocation2());
+  }
+
+  std::string error_msg;
+  ASSERT_TRUE(OatFileAssistant::Dex2Oat(args, &error_msg)) << error_msg;
+
+  if (!relocate) {
+    // Restore the dalvik cache if needed.
+    ASSERT_EQ(0, rename(dalvik_cache_tmp.c_str(), dalvik_cache.c_str())) << strerror(errno);
+  }
+
+  // Verify the odex file was generated as expected.
+  std::unique_ptr<OatFile> odex_file(OatFile::Open(oat_location.c_str(),
+                                                   oat_location.c_str(),
+                                                   nullptr,
+                                                   nullptr,
+                                                   false,
+                                                   /*low_4gb*/false,
+                                                   dex_location.c_str(),
+                                                   &error_msg));
+  ASSERT_TRUE(odex_file.get() != nullptr) << error_msg;
+  EXPECT_EQ(pic, odex_file->IsPic());
+  EXPECT_EQ(filter, odex_file->GetCompilerFilter());
+
+  std::unique_ptr<ImageHeader> image_header(
+          gc::space::ImageSpace::ReadImageHeader(image_location.c_str(),
+                                                 kRuntimeISA,
+                                                 &error_msg));
+  ASSERT_TRUE(image_header != nullptr) << error_msg;
+  const OatHeader& oat_header = odex_file->GetOatHeader();
+  uint32_t combined_checksum = OatFileAssistant::CalculateCombinedImageChecksum();
+
+  if (CompilerFilter::DependsOnImageChecksum(filter)) {
+    if (with_alternate_image) {
+      EXPECT_NE(combined_checksum, oat_header.GetImageFileLocationOatChecksum());
+    } else {
+      EXPECT_EQ(combined_checksum, oat_header.GetImageFileLocationOatChecksum());
+    }
+  }
+
+  if (!with_alternate_image) {
+    if (CompilerFilter::IsBytecodeCompilationEnabled(filter)) {
+      if (relocate) {
+        EXPECT_EQ(reinterpret_cast<uintptr_t>(image_header->GetOatDataBegin()),
+            oat_header.GetImageFileLocationOatDataBegin());
+        EXPECT_EQ(image_header->GetPatchDelta(), oat_header.GetImagePatchDelta());
+      } else {
+        EXPECT_NE(reinterpret_cast<uintptr_t>(image_header->GetOatDataBegin()),
+            oat_header.GetImageFileLocationOatDataBegin());
+        EXPECT_NE(image_header->GetPatchDelta(), oat_header.GetImagePatchDelta());
+      }
+    }
+  }
+}
+
+void DexoptTest::GenerateOdexForTest(const std::string& dex_location,
+                         const std::string& odex_location,
+                         CompilerFilter::Filter filter) {
+  GenerateOatForTest(dex_location,
+                     odex_location,
+                     filter,
+                     /*relocate*/false,
+                     /*pic*/false,
+                     /*with_alternate_image*/false);
+}
+
+void DexoptTest::GeneratePicOdexForTest(const std::string& dex_location,
+                            const std::string& odex_location,
+                            CompilerFilter::Filter filter) {
+  GenerateOatForTest(dex_location,
+                     odex_location,
+                     filter,
+                     /*relocate*/false,
+                     /*pic*/true,
+                     /*with_alternate_image*/false);
+}
+
+void DexoptTest::GenerateOatForTest(const char* dex_location,
+                        CompilerFilter::Filter filter,
+                        bool relocate,
+                        bool pic,
+                        bool with_alternate_image) {
+  std::string oat_location;
+  std::string error_msg;
+  ASSERT_TRUE(OatFileAssistant::DexLocationToOatFilename(
+        dex_location, kRuntimeISA, &oat_location, &error_msg)) << error_msg;
+  GenerateOatForTest(dex_location,
+                     oat_location,
+                     filter,
+                     relocate,
+                     pic,
+                     with_alternate_image);
+}
+
+void DexoptTest::GenerateOatForTest(const char* dex_location, CompilerFilter::Filter filter) {
+  GenerateOatForTest(dex_location,
+                     filter,
+                     /*relocate*/true,
+                     /*pic*/false,
+                     /*with_alternate_image*/false);
+}
+
+bool DexoptTest::PreRelocateImage(const std::string& image_location, std::string* error_msg) {
+  std::string image;
+  if (!GetCachedImageFile(image_location, &image, error_msg)) {
+    return false;
+  }
+
+  std::string patchoat = GetAndroidRoot();
+  patchoat += kIsDebugBuild ? "/bin/patchoatd" : "/bin/patchoat";
+
+  std::vector<std::string> argv;
+  argv.push_back(patchoat);
+  argv.push_back("--input-image-location=" + image_location);
+  argv.push_back("--output-image-file=" + image);
+  argv.push_back("--instruction-set=" + std::string(GetInstructionSetString(kRuntimeISA)));
+  argv.push_back("--base-offset-delta=0x00008000");
+  return Exec(argv, error_msg);
+}
+
+void DexoptTest::ReserveImageSpace() {
+  MemMap::Init();
+
+  // Ensure a chunk of memory is reserved for the image space.
+  // The reservation_end includes room for the main space that has to come
+  // right after the image in case of the GSS collector.
+  uintptr_t reservation_start = ART_BASE_ADDRESS;
+  uintptr_t reservation_end = ART_BASE_ADDRESS + 384 * MB;
+
+  std::unique_ptr<BacktraceMap> map(BacktraceMap::Create(getpid(), true));
+  ASSERT_TRUE(map.get() != nullptr) << "Failed to build process map";
+  for (BacktraceMap::const_iterator it = map->begin();
+      reservation_start < reservation_end && it != map->end(); ++it) {
+    ReserveImageSpaceChunk(reservation_start, std::min(it->start, reservation_end));
+    reservation_start = std::max(reservation_start, it->end);
+  }
+  ReserveImageSpaceChunk(reservation_start, reservation_end);
+}
+
+void DexoptTest::ReserveImageSpaceChunk(uintptr_t start, uintptr_t end) {
+  if (start < end) {
+    std::string error_msg;
+    image_reservation_.push_back(std::unique_ptr<MemMap>(
+        MemMap::MapAnonymous("image reservation",
+            reinterpret_cast<uint8_t*>(start), end - start,
+            PROT_NONE, false, false, &error_msg)));
+    ASSERT_TRUE(image_reservation_.back().get() != nullptr) << error_msg;
+    LOG(INFO) << "Reserved space for image " <<
+      reinterpret_cast<void*>(image_reservation_.back()->Begin()) << "-" <<
+      reinterpret_cast<void*>(image_reservation_.back()->End());
+  }
+}
+
+void DexoptTest::UnreserveImageSpace() {
+  image_reservation_.clear();
+}
+
+}  // namespace art
diff --git a/runtime/dexopt_test.h b/runtime/dexopt_test.h
new file mode 100644
index 0000000..5f0eafd
--- /dev/null
+++ b/runtime/dexopt_test.h
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ART_RUNTIME_DEXOPT_TEST_H_
+#define ART_RUNTIME_DEXOPT_TEST_H_
+
+#include <string>
+#include <vector>
+
+#include "dex2oat_environment_test.h"
+
+namespace art {
+
+class DexoptTest : public Dex2oatEnvironmentTest {
+ public:
+  virtual void SetUp() OVERRIDE;
+
+  virtual void PreRuntimeCreate();
+
+  virtual void PostRuntimeCreate() OVERRIDE;
+
+  // Generate an oat file for the purposes of test.
+  // The oat file will be generated for dex_location in the given oat_location
+  // with the following configuration:
+  //   filter - controls the compilation filter
+  //   pic - whether or not the code will be PIC
+  //   relocate - if true, the oat file will be relocated with respect to the
+  //      boot image. Otherwise the oat file will not be relocated.
+  //   with_alternate_image - if true, the oat file will be generated with an
+  //      image checksum different than the current image checksum.
+  void GenerateOatForTest(const std::string& dex_location,
+                          const std::string& oat_location,
+                          CompilerFilter::Filter filter,
+                          bool relocate,
+                          bool pic,
+                          bool with_alternate_image);
+
+  // Generate a non-PIC odex file for the purposes of test.
+  // The generated odex file will be un-relocated.
+  void GenerateOdexForTest(const std::string& dex_location,
+                           const std::string& odex_location,
+                           CompilerFilter::Filter filter);
+
+  void GeneratePicOdexForTest(const std::string& dex_location,
+                              const std::string& odex_location,
+                              CompilerFilter::Filter filter);
+
+  // Generate an oat file for the given dex location in its oat location (under
+  // the dalvik cache).
+  void GenerateOatForTest(const char* dex_location,
+                          CompilerFilter::Filter filter,
+                          bool relocate,
+                          bool pic,
+                          bool with_alternate_image);
+
+  // Generate a standard oat file in the oat location.
+  void GenerateOatForTest(const char* dex_location, CompilerFilter::Filter filter);
+
+ private:
+  // Pre-Relocate the image to a known non-zero offset so we don't have to
+  // deal with the runtime randomly relocating the image by 0 and messing up
+  // the expected results of the tests.
+  bool PreRelocateImage(const std::string& image_location, std::string* error_msg);
+
+  // Reserve memory around where the image will be loaded so other memory
+  // won't conflict when it comes time to load the image.
+  // This can be called with an already loaded image to reserve the space
+  // around it.
+  void ReserveImageSpace();
+
+  // Reserve a chunk of memory for the image space in the given range.
+  // Only has effect for chunks with a positive number of bytes.
+  void ReserveImageSpaceChunk(uintptr_t start, uintptr_t end);
+
+  // Unreserve any memory reserved by ReserveImageSpace. This should be called
+  // before the image is loaded.
+  void UnreserveImageSpace();
+
+  std::vector<std::unique_ptr<MemMap>> image_reservation_;
+};
+
+}  // namespace art
+
+#endif  // ART_RUNTIME_DEXOPT_TEST_H_
diff --git a/runtime/gc/heap.cc b/runtime/gc/heap.cc
index 34d8284..7044979 100644
--- a/runtime/gc/heap.cc
+++ b/runtime/gc/heap.cc
@@ -360,7 +360,7 @@
     // If we are the zygote, the non moving space becomes the zygote space when we run
     // PreZygoteFork the first time. In this case, call the map "zygote space" since we can't
     // rename the mem map later.
-    const char* space_name = is_zygote ? kZygoteSpaceName: kNonMovingSpaceName;
+    const char* space_name = is_zygote ? kZygoteSpaceName : kNonMovingSpaceName;
     // Reserve the non moving mem map before the other two since it needs to be at a specific
     // address.
     non_moving_space_mem_map.reset(
@@ -3543,8 +3543,11 @@
   collector::GcType gc_type = collector_ran->GetGcType();
   const double multiplier = HeapGrowthMultiplier();  // Use the multiplier to grow more for
   // foreground.
-  const uint64_t adjusted_min_free = static_cast<uint64_t>(min_free_ * multiplier);
-  const uint64_t adjusted_max_free = static_cast<uint64_t>(max_free_ * multiplier);
+  // Ensure at least 2.5 MB to temporarily fix excessive GC caused by TLAB ergonomics.
+  const uint64_t adjusted_min_free = std::max(static_cast<uint64_t>(min_free_ * multiplier),
+                                              static_cast<uint64_t>(5 * MB / 2));
+  const uint64_t adjusted_max_free = std::max(static_cast<uint64_t>(max_free_ * multiplier),
+                                              static_cast<uint64_t>(5 * MB / 2));
   if (gc_type != collector::kGcTypeSticky) {
     // Grow the heap for non sticky GC.
     ssize_t delta = bytes_allocated / GetTargetHeapUtilization() - bytes_allocated;
diff --git a/runtime/gc/reference_processor.h b/runtime/gc/reference_processor.h
index b15544d..38b68cb 100644
--- a/runtime/gc/reference_processor.h
+++ b/runtime/gc/reference_processor.h
@@ -42,7 +42,7 @@
 
 class Heap;
 
-// Used to process java.lang.References concurrently or paused.
+// Used to process java.lang.ref.Reference instances concurrently or paused.
 class ReferenceProcessor {
  public:
   explicit ReferenceProcessor();
diff --git a/runtime/gc/scoped_gc_critical_section.h b/runtime/gc/scoped_gc_critical_section.h
index ec93bca..1271ff7 100644
--- a/runtime/gc/scoped_gc_critical_section.h
+++ b/runtime/gc/scoped_gc_critical_section.h
@@ -27,8 +27,8 @@
 
 namespace gc {
 
-// Wait until the GC is finished and then prevent GC from starting until the destructor. Used
-// to prevent deadlocks in places where we call ClassLinker::VisitClass with all th threads
+// Wait until the GC is finished and then prevent the GC from starting until the destructor. Used
+// to prevent deadlocks in places where we call ClassLinker::VisitClass with all the threads
 // suspended.
 class ScopedGCCriticalSection {
  public:
diff --git a/runtime/gc/space/large_object_space.cc b/runtime/gc/space/large_object_space.cc
index e71a397..4c6b5bf 100644
--- a/runtime/gc/space/large_object_space.cc
+++ b/runtime/gc/space/large_object_space.cc
@@ -141,16 +141,6 @@
     return nullptr;
   }
   mirror::Object* const obj = reinterpret_cast<mirror::Object*>(mem_map->Begin());
-  if (kIsDebugBuild) {
-    ReaderMutexLock mu2(Thread::Current(), *Locks::heap_bitmap_lock_);
-    auto* heap = Runtime::Current()->GetHeap();
-    auto* live_bitmap = heap->GetLiveBitmap();
-    auto* space_bitmap = live_bitmap->GetContinuousSpaceBitmap(obj);
-    CHECK(space_bitmap == nullptr) << obj << " overlaps with bitmap " << *space_bitmap;
-    auto* obj_end = reinterpret_cast<mirror::Object*>(mem_map->End());
-    space_bitmap = live_bitmap->GetContinuousSpaceBitmap(obj_end - 1);
-    CHECK(space_bitmap == nullptr) << obj_end << " overlaps with bitmap " << *space_bitmap;
-  }
   MutexLock mu(self, lock_);
   large_objects_.Put(obj, LargeObject {mem_map, false /* not zygote */});
   const size_t allocation_size = mem_map->BaseSize();
diff --git a/runtime/hprof/hprof.cc b/runtime/hprof/hprof.cc
index fe6a6e9..3d3ad59 100644
--- a/runtime/hprof/hprof.cc
+++ b/runtime/hprof/hprof.cc
@@ -1169,7 +1169,7 @@
 }
 
 void Hprof::DumpHeapClass(mirror::Class* klass) {
-  if (!klass->IsResolved() && !klass->IsErroneous()) {
+  if (!klass->IsResolved()) {
     // Class is allocated but not yet resolved: we cannot access its fields or super class.
     return;
   }
diff --git a/runtime/image.cc b/runtime/image.cc
index 6d88895..54b099e 100644
--- a/runtime/image.cc
+++ b/runtime/image.cc
@@ -25,7 +25,7 @@
 namespace art {
 
 const uint8_t ImageHeader::kImageMagic[] = { 'a', 'r', 't', '\n' };
-const uint8_t ImageHeader::kImageVersion[] = { '0', '3', '5', '\0' };  // ArtMethod update
+const uint8_t ImageHeader::kImageVersion[] = { '0', '3', '6', '\0' };  // Erroneous resolved class.
 
 ImageHeader::ImageHeader(uint32_t image_begin,
                          uint32_t image_size,
diff --git a/runtime/instrumentation.cc b/runtime/instrumentation.cc
index 4ea1130..bbd6d35 100644
--- a/runtime/instrumentation.cc
+++ b/runtime/instrumentation.cc
@@ -88,11 +88,11 @@
 }
 
 void Instrumentation::InstallStubsForClass(mirror::Class* klass) {
-  if (klass->IsErroneous()) {
-    // We can't execute code in a erroneous class: do nothing.
-  } else if (!klass->IsResolved()) {
+  if (!klass->IsResolved()) {
     // We need the class to be resolved to install/uninstall stubs. Otherwise its methods
     // could not be initialized or linked with regards to class inheritance.
+  } else if (klass->IsErroneousResolved()) {
+    // We can't execute code in a erroneous class: do nothing.
   } else {
     for (ArtMethod& method : klass->GetMethods(kRuntimePointerSize)) {
       InstallStubsForMethod(&method);
diff --git a/runtime/interpreter/interpreter_common.cc b/runtime/interpreter/interpreter_common.cc
index 76777d9..28bcb97 100644
--- a/runtime/interpreter/interpreter_common.cc
+++ b/runtime/interpreter/interpreter_common.cc
@@ -596,7 +596,7 @@
     // Get the register arguments for the invoke.
     inst->GetVarArgs(args, inst_data);
     // Drop the first register which is the method handle performing the invoke.
-    memcpy(args, args + 1, sizeof(args[0]) * (Instruction::kMaxVarArgRegs - 1));
+    memmove(args, args + 1, sizeof(args[0]) * (Instruction::kMaxVarArgRegs - 1));
     args[Instruction::kMaxVarArgRegs - 1] = 0;
     return DoInvokePolymorphic<is_range, do_access_check>(self,
                                                           invoke_method,
diff --git a/runtime/interpreter/mterp/arm64/op_iput_wide_quick.S b/runtime/interpreter/mterp/arm64/op_iput_wide_quick.S
index 6cec363..28e831a 100644
--- a/runtime/interpreter/mterp/arm64/op_iput_wide_quick.S
+++ b/runtime/interpreter/mterp/arm64/op_iput_wide_quick.S
@@ -4,7 +4,7 @@
     GET_VREG w2, w2                     // w2<- fp[B], the object pointer
     ubfx    w0, wINST, #8, #4           // w0<- A
     cbz     w2, common_errNullObject    // object was null
-    GET_VREG_WIDE x0, w0                // x0-< fp[A]
+    GET_VREG_WIDE x0, w0                // x0<- fp[A]
     FETCH_ADVANCE_INST 2                // advance rPC, load wINST
     str     x0, [x2, x3]                // obj.field<- x0
     GET_INST_OPCODE ip                  // extract opcode from wINST
diff --git a/runtime/interpreter/mterp/out/mterp_arm64.S b/runtime/interpreter/mterp/out/mterp_arm64.S
index 681790d..7d442c0 100644
--- a/runtime/interpreter/mterp/out/mterp_arm64.S
+++ b/runtime/interpreter/mterp/out/mterp_arm64.S
@@ -6593,7 +6593,7 @@
     GET_VREG w2, w2                     // w2<- fp[B], the object pointer
     ubfx    w0, wINST, #8, #4           // w0<- A
     cbz     w2, common_errNullObject    // object was null
-    GET_VREG_WIDE x0, w0                // x0-< fp[A]
+    GET_VREG_WIDE x0, w0                // x0<- fp[A]
     FETCH_ADVANCE_INST 2                // advance rPC, load wINST
     str     x0, [x2, x3]                // obj.field<- x0
     GET_INST_OPCODE ip                  // extract opcode from wINST
diff --git a/runtime/interpreter/unstarted_runtime.cc b/runtime/interpreter/unstarted_runtime.cc
index 7dd3d3d..feb6e08 100644
--- a/runtime/interpreter/unstarted_runtime.cc
+++ b/runtime/interpreter/unstarted_runtime.cc
@@ -1443,7 +1443,7 @@
 
   ObjPtr<mirror::Object> java_method_obj = shadow_frame->GetVRegReference(arg_offset);
   ScopedLocalRef<jobject> java_method(env,
-      java_method_obj == nullptr ? nullptr :env->AddLocalReference<jobject>(java_method_obj));
+      java_method_obj == nullptr ? nullptr : env->AddLocalReference<jobject>(java_method_obj));
 
   ObjPtr<mirror::Object> java_receiver_obj = shadow_frame->GetVRegReference(arg_offset + 1);
   ScopedLocalRef<jobject> java_receiver(env,
diff --git a/runtime/jit/jit.cc b/runtime/jit/jit.cc
index b7125a8..6deb03d 100644
--- a/runtime/jit/jit.cc
+++ b/runtime/jit/jit.cc
@@ -26,7 +26,7 @@
 #include "jit_code_cache.h"
 #include "oat_file_manager.h"
 #include "oat_quick_method_header.h"
-#include "offline_profiling_info.h"
+#include "profile_compilation_info.h"
 #include "profile_saver.h"
 #include "runtime.h"
 #include "runtime_options.h"
@@ -514,7 +514,7 @@
       }
     }
 
-    native_pc = stack_map.GetNativePcOffset(encoding.stack_map_encoding) +
+    native_pc = stack_map.GetNativePcOffset(encoding.stack_map_encoding, kRuntimeISA) +
         osr_method->GetEntryPoint();
     VLOG(jit) << "Jumping to "
               << method_name
diff --git a/runtime/jit/jit.h b/runtime/jit/jit.h
index 05c3905..4112142 100644
--- a/runtime/jit/jit.h
+++ b/runtime/jit/jit.h
@@ -25,7 +25,7 @@
 #include "jit/profile_saver_options.h"
 #include "obj_ptr.h"
 #include "object_callbacks.h"
-#include "offline_profiling_info.h"
+#include "profile_compilation_info.h"
 #include "thread_pool.h"
 
 namespace art {
diff --git a/runtime/jit/offline_profiling_info.cc b/runtime/jit/profile_compilation_info.cc
similarity index 99%
rename from runtime/jit/offline_profiling_info.cc
rename to runtime/jit/profile_compilation_info.cc
index 6f2a8c6..1405c40 100644
--- a/runtime/jit/offline_profiling_info.cc
+++ b/runtime/jit/profile_compilation_info.cc
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#include "offline_profiling_info.h"
+#include "profile_compilation_info.h"
 
 #include "errno.h"
 #include <limits.h>
diff --git a/runtime/jit/offline_profiling_info.h b/runtime/jit/profile_compilation_info.h
similarity index 97%
rename from runtime/jit/offline_profiling_info.h
rename to runtime/jit/profile_compilation_info.h
index 53d0eea..f8061bc 100644
--- a/runtime/jit/offline_profiling_info.h
+++ b/runtime/jit/profile_compilation_info.h
@@ -14,8 +14,8 @@
  * limitations under the License.
  */
 
-#ifndef ART_RUNTIME_JIT_OFFLINE_PROFILING_INFO_H_
-#define ART_RUNTIME_JIT_OFFLINE_PROFILING_INFO_H_
+#ifndef ART_RUNTIME_JIT_PROFILE_COMPILATION_INFO_H_
+#define ART_RUNTIME_JIT_PROFILE_COMPILATION_INFO_H_
 
 #include <set>
 #include <vector>
@@ -29,7 +29,6 @@
 
 namespace art {
 
-// TODO: rename file.
 /**
  * Profile information in a format suitable to be queried by the compiler and
  * performing profile guided compilation.
@@ -187,4 +186,4 @@
 
 }  // namespace art
 
-#endif  // ART_RUNTIME_JIT_OFFLINE_PROFILING_INFO_H_
+#endif  // ART_RUNTIME_JIT_PROFILE_COMPILATION_INFO_H_
diff --git a/runtime/jit/profile_compilation_info_test.cc b/runtime/jit/profile_compilation_info_test.cc
index 1dd1e36..835a5f3 100644
--- a/runtime/jit/profile_compilation_info_test.cc
+++ b/runtime/jit/profile_compilation_info_test.cc
@@ -25,7 +25,7 @@
 #include "mirror/class-inl.h"
 #include "mirror/class_loader.h"
 #include "handle_scope-inl.h"
-#include "jit/offline_profiling_info.h"
+#include "jit/profile_compilation_info.h"
 #include "scoped_thread_state_change-inl.h"
 
 namespace art {
diff --git a/runtime/jit/profile_saver.h b/runtime/jit/profile_saver.h
index 59e2c94..9c5e41f 100644
--- a/runtime/jit/profile_saver.h
+++ b/runtime/jit/profile_saver.h
@@ -19,7 +19,7 @@
 
 #include "base/mutex.h"
 #include "jit_code_cache.h"
-#include "offline_profiling_info.h"
+#include "profile_compilation_info.h"
 #include "profile_saver_options.h"
 #include "safe_map.h"
 
diff --git a/runtime/mirror/class.cc b/runtime/mirror/class.cc
index 9964b73..f08d4da 100644
--- a/runtime/mirror/class.cc
+++ b/runtime/mirror/class.cc
@@ -115,7 +115,9 @@
   ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
   bool class_linker_initialized = class_linker != nullptr && class_linker->IsInitialized();
   if (LIKELY(class_linker_initialized)) {
-    if (UNLIKELY(new_status <= old_status && new_status != kStatusError &&
+    if (UNLIKELY(new_status <= old_status &&
+                 new_status != kStatusErrorUnresolved &&
+                 new_status != kStatusErrorResolved &&
                  new_status != kStatusRetired)) {
       LOG(FATAL) << "Unexpected change back of class status for " << h_this->PrettyClass()
                  << " " << old_status << " -> " << new_status;
@@ -127,10 +129,12 @@
             << h_this->PrettyClass() << " " << old_status << " -> " << new_status;
     }
   }
-  if (UNLIKELY(new_status == kStatusError)) {
-    CHECK_NE(h_this->GetStatus(), kStatusError)
+  if (UNLIKELY(IsErroneous(new_status))) {
+    CHECK(!h_this->IsErroneous())
         << "Attempt to set as erroneous an already erroneous class "
-        << h_this->PrettyClass();
+        << h_this->PrettyClass()
+        << " old_status: " << old_status << " new_status: " << new_status;
+    CHECK_EQ(new_status == kStatusErrorResolved, old_status >= kStatusResolved);
     if (VLOG_IS_ON(class_linker)) {
       LOG(ERROR) << "Setting " << h_this->PrettyDescriptor() << " to erroneous.";
       if (self->IsExceptionPending()) {
@@ -177,7 +181,7 @@
       // Class is a temporary one, ensure that waiters for resolution get notified of retirement
       // so that they can grab the new version of the class from the class linker's table.
       CHECK_LT(new_status, kStatusResolved) << h_this->PrettyDescriptor();
-      if (new_status == kStatusRetired || new_status == kStatusError) {
+      if (new_status == kStatusRetired || new_status == kStatusErrorUnresolved) {
         h_this->NotifyAll(self);
       }
     } else {
@@ -305,7 +309,7 @@
     }
     if (h_this->NumStaticFields() > 0) {
       os << "  static fields (" << h_this->NumStaticFields() << " entries):\n";
-      if (h_this->IsResolved() || h_this->IsErroneous()) {
+      if (h_this->IsResolved()) {
         for (size_t i = 0; i < h_this->NumStaticFields(); ++i) {
           os << StringPrintf("    %2zd: %s\n", i,
                              ArtField::PrettyField(h_this->GetStaticField(i)).c_str());
@@ -316,7 +320,7 @@
     }
     if (h_this->NumInstanceFields() > 0) {
       os << "  instance fields (" << h_this->NumInstanceFields() << " entries):\n";
-      if (h_this->IsResolved() || h_this->IsErroneous()) {
+      if (h_this->IsResolved()) {
         for (size_t i = 0; i < h_this->NumInstanceFields(); ++i) {
           os << StringPrintf("    %2zd: %s\n", i,
                              ArtField::PrettyField(h_this->GetInstanceField(i)).c_str());
diff --git a/runtime/mirror/class.h b/runtime/mirror/class.h
index fb2792a..7f6aa12 100644
--- a/runtime/mirror/class.h
+++ b/runtime/mirror/class.h
@@ -84,6 +84,13 @@
   // will be gc'ed once all refs to the class point to the newly
   // cloned version.
   //
+  // kStatusErrorUnresolved, kStatusErrorResolved: Class is erroneous. We need
+  // to distinguish between classes that have been resolved and classes that
+  // have not. This is important because the const-class instruction needs to
+  // return a previously resolved class even if its subsequent initialization
+  // failed. We also need this to decide whether to wrap a previous
+  // initialization failure in ClassDefNotFound error or not.
+  //
   // kStatusNotReady: If a Class cannot be found in the class table by
   // FindClass, it allocates an new one with AllocClass in the
   // kStatusNotReady and calls LoadClass. Note if it does find a
@@ -119,8 +126,9 @@
   //
   // TODO: Explain the other states
   enum Status {
-    kStatusRetired = -2,  // Retired, should not be used. Use the newly cloned one instead.
-    kStatusError = -1,
+    kStatusRetired = -3,  // Retired, should not be used. Use the newly cloned one instead.
+    kStatusErrorResolved = -2,
+    kStatusErrorUnresolved = -1,
     kStatusNotReady = 0,
     kStatusIdx = 1,  // Loaded, DEX idx in super_class_type_idx_ and interfaces_type_idx_.
     kStatusLoaded = 2,  // DEX idx values resolved.
@@ -158,8 +166,25 @@
 
   // Returns true if the class has failed to link.
   template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags>
+  bool IsErroneousUnresolved() REQUIRES_SHARED(Locks::mutator_lock_) {
+    return GetStatus<kVerifyFlags>() == kStatusErrorUnresolved;
+  }
+
+  // Returns true if the class has failed to initialize.
+  template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags>
+  bool IsErroneousResolved() REQUIRES_SHARED(Locks::mutator_lock_) {
+    return GetStatus<kVerifyFlags>() == kStatusErrorResolved;
+  }
+
+  // Returns true if the class status indicets that the class has failed to link or initialize.
+  static bool IsErroneous(Status status) {
+    return status == kStatusErrorUnresolved || status == kStatusErrorResolved;
+  }
+
+  // Returns true if the class has failed to link or initialize.
+  template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags>
   bool IsErroneous() REQUIRES_SHARED(Locks::mutator_lock_) {
-    return GetStatus<kVerifyFlags>() == kStatusError;
+    return IsErroneous(GetStatus<kVerifyFlags>());
   }
 
   // Returns true if the class has been loaded.
@@ -177,7 +202,8 @@
   // Returns true if the class has been linked.
   template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags>
   bool IsResolved() REQUIRES_SHARED(Locks::mutator_lock_) {
-    return GetStatus<kVerifyFlags>() >= kStatusResolved;
+    Status status = GetStatus<kVerifyFlags>();
+    return status >= kStatusResolved || status == kStatusErrorResolved;
   }
 
   // Returns true if the class was compile-time verified.
@@ -345,7 +371,7 @@
   // be replaced with a class with the right size for embedded imt/vtable.
   bool IsTemp() REQUIRES_SHARED(Locks::mutator_lock_) {
     Status s = GetStatus();
-    return s < Status::kStatusResolving && ShouldHaveEmbeddedVTable();
+    return s < Status::kStatusResolving && s != kStatusErrorResolved && ShouldHaveEmbeddedVTable();
   }
 
   String* GetName() REQUIRES_SHARED(Locks::mutator_lock_);  // Returns the cached name.
@@ -1017,7 +1043,7 @@
   // Returns the number of instance fields containing reference types. Does not count fields in any
   // super classes.
   uint32_t NumReferenceInstanceFields() REQUIRES_SHARED(Locks::mutator_lock_) {
-    DCHECK(IsResolved() || IsErroneous());
+    DCHECK(IsResolved());
     return GetField32(OFFSET_OF_OBJECT_MEMBER(Class, num_reference_instance_fields_));
   }
 
@@ -1045,7 +1071,7 @@
 
   // Returns the number of static fields containing reference types.
   uint32_t NumReferenceStaticFields() REQUIRES_SHARED(Locks::mutator_lock_) {
-    DCHECK(IsResolved() || IsErroneous());
+    DCHECK(IsResolved());
     return GetField32(OFFSET_OF_OBJECT_MEMBER(Class, num_reference_static_fields_));
   }
 
diff --git a/runtime/mirror/class_ext.cc b/runtime/mirror/class_ext.cc
index 7c6a710..efd949e 100644
--- a/runtime/mirror/class_ext.cc
+++ b/runtime/mirror/class_ext.cc
@@ -113,6 +113,11 @@
   }
 }
 
+void ClassExt::SetOriginalDexFileBytes(ObjPtr<ByteArray> bytes) {
+  DCHECK(!Runtime::Current()->IsActiveTransaction());
+  SetFieldObject<false>(OFFSET_OF_OBJECT_MEMBER(ClassExt, original_dex_file_bytes_), bytes);
+}
+
 void ClassExt::SetClass(ObjPtr<Class> dalvik_system_ClassExt) {
   CHECK(dalvik_system_ClassExt != nullptr);
   dalvik_system_ClassExt_ = GcRoot<Class>(dalvik_system_ClassExt);
diff --git a/runtime/mirror/class_ext.h b/runtime/mirror/class_ext.h
index 9104631..ad8a61b 100644
--- a/runtime/mirror/class_ext.h
+++ b/runtime/mirror/class_ext.h
@@ -61,6 +61,12 @@
     return GetFieldObject<PointerArray>(OFFSET_OF_OBJECT_MEMBER(ClassExt, obsolete_methods_));
   }
 
+  ByteArray* GetOriginalDexFileBytes() REQUIRES_SHARED(Locks::mutator_lock_) {
+    return GetFieldObject<ByteArray>(OFFSET_OF_OBJECT_MEMBER(ClassExt, original_dex_file_bytes_));
+  }
+
+  void SetOriginalDexFileBytes(ObjPtr<ByteArray> bytes) REQUIRES_SHARED(Locks::mutator_lock_);
+
   void SetObsoleteArrays(ObjPtr<PointerArray> methods, ObjPtr<ObjectArray<DexCache>> dex_caches)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
@@ -80,7 +86,7 @@
 
   HeapReference<PointerArray> obsolete_methods_;
 
-  HeapReference<DexCache> original_dex_cache_;
+  HeapReference<ByteArray> original_dex_file_bytes_;
 
   // The saved verification error of this class.
   HeapReference<Object> verify_error_;
diff --git a/runtime/monitor.cc b/runtime/monitor.cc
index 9c09275..071b0e2 100644
--- a/runtime/monitor.cc
+++ b/runtime/monitor.cc
@@ -1394,6 +1394,12 @@
   }
 }
 
+size_t MonitorList::Size() {
+  Thread* self = Thread::Current();
+  MutexLock mu(self, monitor_list_lock_);
+  return list_.size();
+}
+
 class MonitorDeflateVisitor : public IsMarkedVisitor {
  public:
   MonitorDeflateVisitor() : self_(Thread::Current()), deflate_count_(0) {}
diff --git a/runtime/monitor.h b/runtime/monitor.h
index c3da563..1fa4682 100644
--- a/runtime/monitor.h
+++ b/runtime/monitor.h
@@ -331,6 +331,7 @@
   void BroadcastForNewMonitors() REQUIRES(!monitor_list_lock_);
   // Returns how many monitors were deflated.
   size_t DeflateMonitors() REQUIRES(!monitor_list_lock_) REQUIRES(Locks::mutator_lock_);
+  size_t Size() REQUIRES(!monitor_list_lock_);
 
   typedef std::list<Monitor*, TrackingAllocator<Monitor*, kAllocatorTagMonitorList>> Monitors;
 
diff --git a/runtime/native/java_lang_VMClassLoader.cc b/runtime/native/java_lang_VMClassLoader.cc
index 284d2d1..a8fa7db 100644
--- a/runtime/native/java_lang_VMClassLoader.cc
+++ b/runtime/native/java_lang_VMClassLoader.cc
@@ -81,14 +81,12 @@
   if (c != nullptr && c->IsErroneous()) {
     cl->ThrowEarlierClassFailure(c.Ptr());
     Thread* self = soa.Self();
-    ObjPtr<mirror::Class> eiie_class =
-        self->DecodeJObject(WellKnownClasses::java_lang_ExceptionInInitializerError)->AsClass();
     ObjPtr<mirror::Class> iae_class =
         self->DecodeJObject(WellKnownClasses::java_lang_IllegalAccessError)->AsClass();
     ObjPtr<mirror::Class> ncdfe_class =
         self->DecodeJObject(WellKnownClasses::java_lang_NoClassDefFoundError)->AsClass();
     ObjPtr<mirror::Class> exception = self->GetException()->GetClass();
-    if (exception == eiie_class || exception == iae_class || exception == ncdfe_class) {
+    if (exception == iae_class || exception == ncdfe_class) {
       self->ThrowNewWrappedException("Ljava/lang/ClassNotFoundException;",
                                      c->PrettyDescriptor().c_str());
     }
diff --git a/runtime/oat.h b/runtime/oat.h
index 3b3ab5a..29821a2 100644
--- a/runtime/oat.h
+++ b/runtime/oat.h
@@ -32,7 +32,7 @@
 class PACKED(4) OatHeader {
  public:
   static constexpr uint8_t kOatMagic[] = { 'o', 'a', 't', '\n' };
-  static constexpr uint8_t kOatVersion[] = { '1', '0', '1', '\0' };  // Array entrypoints change
+  static constexpr uint8_t kOatVersion[] = { '1', '0', '3', '\0' };  // Native pc change
 
   static constexpr const char* kImageLocationKey = "image-location";
   static constexpr const char* kDex2OatCmdLineKey = "dex2oat-cmdline";
diff --git a/runtime/oat_file.h b/runtime/oat_file.h
index 62d99fb..111755e 100644
--- a/runtime/oat_file.h
+++ b/runtime/oat_file.h
@@ -201,8 +201,12 @@
     // A representation of an invalid OatClass, used when an OatClass can't be found.
     // See FindOatClass().
     static OatClass Invalid() {
-      return OatClass(nullptr, mirror::Class::kStatusError, kOatClassNoneCompiled, 0, nullptr,
-                      nullptr);
+      return OatClass(/* oat_file */ nullptr,
+                      mirror::Class::kStatusErrorUnresolved,
+                      kOatClassNoneCompiled,
+                      /* bitmap_size */ 0,
+                      /* bitmap_pointer */ nullptr,
+                      /* methods_pointer */ nullptr);
     }
 
    private:
diff --git a/runtime/oat_file_assistant_test.cc b/runtime/oat_file_assistant_test.cc
index afa804c..5772008 100644
--- a/runtime/oat_file_assistant_test.cc
+++ b/runtime/oat_file_assistant_test.cc
@@ -14,23 +14,16 @@
  * limitations under the License.
  */
 
-#include <algorithm>
-#include <fstream>
 #include <string>
 #include <vector>
 #include <sys/param.h>
 
 #include "android-base/strings.h"
-#include <backtrace/BacktraceMap.h>
 #include <gtest/gtest.h>
 
 #include "art_field-inl.h"
 #include "class_linker-inl.h"
-#include "common_runtime_test.h"
-#include "compiler_callbacks.h"
-#include "dex2oat_environment_test.h"
-#include "gc/space/image_space.h"
-#include "mem_map.h"
+#include "dexopt_test.h"
 #include "oat_file_assistant.h"
 #include "oat_file_manager.h"
 #include "os.h"
@@ -40,240 +33,17 @@
 
 namespace art {
 
-class OatFileAssistantTest : public Dex2oatEnvironmentTest {
- public:
-  virtual void SetUp() OVERRIDE {
-    ReserveImageSpace();
-    Dex2oatEnvironmentTest::SetUp();
-  }
+class OatFileAssistantTest : public DexoptTest {};
 
-  // Pre-Relocate the image to a known non-zero offset so we don't have to
-  // deal with the runtime randomly relocating the image by 0 and messing up
-  // the expected results of the tests.
-  bool PreRelocateImage(const std::string& image_location, std::string* error_msg) {
-    std::string image;
-    if (!GetCachedImageFile(image_location, &image, error_msg)) {
-      return false;
-    }
-
-    std::string patchoat = GetAndroidRoot();
-    patchoat += kIsDebugBuild ? "/bin/patchoatd" : "/bin/patchoat";
-
-    std::vector<std::string> argv;
-    argv.push_back(patchoat);
-    argv.push_back("--input-image-location=" + image_location);
-    argv.push_back("--output-image-file=" + image);
-    argv.push_back("--instruction-set=" + std::string(GetInstructionSetString(kRuntimeISA)));
-    argv.push_back("--base-offset-delta=0x00008000");
-    return Exec(argv, error_msg);
-  }
-
-  virtual void PreRuntimeCreate() {
-    std::string error_msg;
-    ASSERT_TRUE(PreRelocateImage(GetImageLocation(), &error_msg)) << error_msg;
-    ASSERT_TRUE(PreRelocateImage(GetImageLocation2(), &error_msg)) << error_msg;
-    UnreserveImageSpace();
-  }
-
-  virtual void PostRuntimeCreate() OVERRIDE {
-    ReserveImageSpace();
-  }
-
-  // Generate an oat file for the purposes of test.
-  void GenerateOatForTest(const std::string& dex_location,
-                          const std::string& oat_location,
-                          CompilerFilter::Filter filter,
-                          bool relocate,
-                          bool pic,
-                          bool with_alternate_image) {
-    std::string dalvik_cache = GetDalvikCache(GetInstructionSetString(kRuntimeISA));
-    std::string dalvik_cache_tmp = dalvik_cache + ".redirected";
-
-    if (!relocate) {
-      // Temporarily redirect the dalvik cache so dex2oat doesn't find the
-      // relocated image file.
-      ASSERT_EQ(0, rename(dalvik_cache.c_str(), dalvik_cache_tmp.c_str())) << strerror(errno);
-    }
-
-    std::vector<std::string> args;
-    args.push_back("--dex-file=" + dex_location);
-    args.push_back("--oat-file=" + oat_location);
-    args.push_back("--compiler-filter=" + CompilerFilter::NameOfFilter(filter));
-    args.push_back("--runtime-arg");
-
-    // Use -Xnorelocate regardless of the relocate argument.
-    // We control relocation by redirecting the dalvik cache when needed
-    // rather than use this flag.
-    args.push_back("-Xnorelocate");
-
-    if (pic) {
-      args.push_back("--compile-pic");
-    }
-
-    std::string image_location = GetImageLocation();
-    if (with_alternate_image) {
-      args.push_back("--boot-image=" + GetImageLocation2());
-    }
-
-    std::string error_msg;
-    ASSERT_TRUE(OatFileAssistant::Dex2Oat(args, &error_msg)) << error_msg;
-
-    if (!relocate) {
-      // Restore the dalvik cache if needed.
-      ASSERT_EQ(0, rename(dalvik_cache_tmp.c_str(), dalvik_cache.c_str())) << strerror(errno);
-    }
-
-    // Verify the odex file was generated as expected.
-    std::unique_ptr<OatFile> odex_file(OatFile::Open(oat_location.c_str(),
-                                                     oat_location.c_str(),
-                                                     nullptr,
-                                                     nullptr,
-                                                     false,
-                                                     /*low_4gb*/false,
-                                                     dex_location.c_str(),
-                                                     &error_msg));
-    ASSERT_TRUE(odex_file.get() != nullptr) << error_msg;
-    EXPECT_EQ(pic, odex_file->IsPic());
-    EXPECT_EQ(filter, odex_file->GetCompilerFilter());
-
-    std::unique_ptr<ImageHeader> image_header(
-            gc::space::ImageSpace::ReadImageHeader(image_location.c_str(),
-                                                   kRuntimeISA,
-                                                   &error_msg));
-    ASSERT_TRUE(image_header != nullptr) << error_msg;
-    const OatHeader& oat_header = odex_file->GetOatHeader();
-    uint32_t combined_checksum = OatFileAssistant::CalculateCombinedImageChecksum();
-
-    if (CompilerFilter::DependsOnImageChecksum(filter)) {
-      if (with_alternate_image) {
-        EXPECT_NE(combined_checksum, oat_header.GetImageFileLocationOatChecksum());
-      } else {
-        EXPECT_EQ(combined_checksum, oat_header.GetImageFileLocationOatChecksum());
-      }
-    }
-
-    if (CompilerFilter::IsBytecodeCompilationEnabled(filter)) {
-      if (relocate) {
-        EXPECT_EQ(reinterpret_cast<uintptr_t>(image_header->GetOatDataBegin()),
-            oat_header.GetImageFileLocationOatDataBegin());
-        EXPECT_EQ(image_header->GetPatchDelta(), oat_header.GetImagePatchDelta());
-      } else {
-        EXPECT_NE(reinterpret_cast<uintptr_t>(image_header->GetOatDataBegin()),
-            oat_header.GetImageFileLocationOatDataBegin());
-        EXPECT_NE(image_header->GetPatchDelta(), oat_header.GetImagePatchDelta());
-      }
-    }
-  }
-
-  // Generate a non-PIC odex file for the purposes of test.
-  // The generated odex file will be un-relocated.
-  void GenerateOdexForTest(const std::string& dex_location,
-                           const std::string& odex_location,
-                           CompilerFilter::Filter filter) {
-    GenerateOatForTest(dex_location,
-                       odex_location,
-                       filter,
-                       /*relocate*/false,
-                       /*pic*/false,
-                       /*with_alternate_image*/false);
-  }
-
-  void GeneratePicOdexForTest(const std::string& dex_location,
-                              const std::string& odex_location,
-                              CompilerFilter::Filter filter) {
-    GenerateOatForTest(dex_location,
-                       odex_location,
-                       filter,
-                       /*relocate*/false,
-                       /*pic*/true,
-                       /*with_alternate_image*/false);
-  }
-
-  // Generate an oat file in the oat location.
-  void GenerateOatForTest(const char* dex_location,
-                          CompilerFilter::Filter filter,
-                          bool relocate,
-                          bool pic,
-                          bool with_alternate_image) {
-    std::string oat_location;
-    std::string error_msg;
-    ASSERT_TRUE(OatFileAssistant::DexLocationToOatFilename(
-          dex_location, kRuntimeISA, &oat_location, &error_msg)) << error_msg;
-    GenerateOatForTest(dex_location,
-                       oat_location,
-                       filter,
-                       relocate,
-                       pic,
-                       with_alternate_image);
-  }
-
-  // Generate a standard oat file in the oat location.
-  void GenerateOatForTest(const char* dex_location, CompilerFilter::Filter filter) {
-    GenerateOatForTest(dex_location,
-                       filter,
-                       /*relocate*/true,
-                       /*pic*/false,
-                       /*with_alternate_image*/false);
-  }
-
- private:
-  // Reserve memory around where the image will be loaded so other memory
-  // won't conflict when it comes time to load the image.
-  // This can be called with an already loaded image to reserve the space
-  // around it.
-  void ReserveImageSpace() {
-    MemMap::Init();
-
-    // Ensure a chunk of memory is reserved for the image space.
-    // The reservation_end includes room for the main space that has to come
-    // right after the image in case of the GSS collector.
-    uintptr_t reservation_start = ART_BASE_ADDRESS;
-    uintptr_t reservation_end = ART_BASE_ADDRESS + 384 * MB;
-
-    std::unique_ptr<BacktraceMap> map(BacktraceMap::Create(getpid(), true));
-    ASSERT_TRUE(map.get() != nullptr) << "Failed to build process map";
-    for (BacktraceMap::const_iterator it = map->begin();
-        reservation_start < reservation_end && it != map->end(); ++it) {
-      ReserveImageSpaceChunk(reservation_start, std::min(it->start, reservation_end));
-      reservation_start = std::max(reservation_start, it->end);
-    }
-    ReserveImageSpaceChunk(reservation_start, reservation_end);
-  }
-
-  // Reserve a chunk of memory for the image space in the given range.
-  // Only has effect for chunks with a positive number of bytes.
-  void ReserveImageSpaceChunk(uintptr_t start, uintptr_t end) {
-    if (start < end) {
-      std::string error_msg;
-      image_reservation_.push_back(std::unique_ptr<MemMap>(
-          MemMap::MapAnonymous("image reservation",
-              reinterpret_cast<uint8_t*>(start), end - start,
-              PROT_NONE, false, false, &error_msg)));
-      ASSERT_TRUE(image_reservation_.back().get() != nullptr) << error_msg;
-      LOG(INFO) << "Reserved space for image " <<
-        reinterpret_cast<void*>(image_reservation_.back()->Begin()) << "-" <<
-        reinterpret_cast<void*>(image_reservation_.back()->End());
-    }
-  }
-
-
-  // Unreserve any memory reserved by ReserveImageSpace. This should be called
-  // before the image is loaded.
-  void UnreserveImageSpace() {
-    image_reservation_.clear();
-  }
-
-  std::vector<std::unique_ptr<MemMap>> image_reservation_;
-};
-
-class OatFileAssistantNoDex2OatTest : public OatFileAssistantTest {
+class OatFileAssistantNoDex2OatTest : public DexoptTest {
  public:
   virtual void SetUpRuntimeOptions(RuntimeOptions* options) {
-    OatFileAssistantTest::SetUpRuntimeOptions(options);
+    DexoptTest::SetUpRuntimeOptions(options);
     options->push_back(std::make_pair("-Xnodex2oat", nullptr));
   }
 };
 
+
 // Case: We have a DEX file, but no OAT file for it.
 // Expect: The status is kDex2OatNeeded.
 TEST_F(OatFileAssistantTest, DexNoOat) {
diff --git a/runtime/oat_quick_method_header.cc b/runtime/oat_quick_method_header.cc
index 9c2378d..fd84426 100644
--- a/runtime/oat_quick_method_header.cc
+++ b/runtime/oat_quick_method_header.cc
@@ -80,7 +80,7 @@
                                    : code_info.GetStackMapForDexPc(dex_pc, encoding);
   if (stack_map.IsValid()) {
     return reinterpret_cast<uintptr_t>(entry_point) +
-           stack_map.GetNativePcOffset(encoding.stack_map_encoding);
+           stack_map.GetNativePcOffset(encoding.stack_map_encoding, kRuntimeISA);
   }
   if (abort_on_failure) {
     ScopedObjectAccess soa(Thread::Current());
diff --git a/runtime/openjdkjvmti/Android.bp b/runtime/openjdkjvmti/Android.bp
index d5c6520..976a1e7 100644
--- a/runtime/openjdkjvmti/Android.bp
+++ b/runtime/openjdkjvmti/Android.bp
@@ -21,12 +21,15 @@
            "object_tagging.cc",
            "OpenjdkJvmTi.cc",
            "ti_class.cc",
+           "ti_class_definition.cc",
+           "ti_dump.cc",
            "ti_field.cc",
            "ti_heap.cc",
            "ti_jni.cc",
            "ti_method.cc",
            "ti_monitor.cc",
            "ti_object.cc",
+           "ti_phase.cc",
            "ti_properties.cc",
            "ti_search.cc",
            "ti_stack.cc",
diff --git a/runtime/openjdkjvmti/OpenjdkJvmTi.cc b/runtime/openjdkjvmti/OpenjdkJvmTi.cc
index 90467db..417d104 100644
--- a/runtime/openjdkjvmti/OpenjdkJvmTi.cc
+++ b/runtime/openjdkjvmti/OpenjdkJvmTi.cc
@@ -49,12 +49,14 @@
 #include "thread-inl.h"
 #include "thread_list.h"
 #include "ti_class.h"
+#include "ti_dump.h"
 #include "ti_field.h"
 #include "ti_heap.h"
 #include "ti_jni.h"
 #include "ti_method.h"
 #include "ti_monitor.h"
 #include "ti_object.h"
+#include "ti_phase.h"
 #include "ti_properties.h"
 #include "ti_redefine.h"
 #include "ti_search.h"
@@ -194,15 +196,15 @@
                                    jvmtiStartFunction proc,
                                    const void* arg,
                                    jint priority) {
-    return ERR(NOT_IMPLEMENTED);
+    return ThreadUtil::RunAgentThread(env, thread, proc, arg, priority);
   }
 
   static jvmtiError SetThreadLocalStorage(jvmtiEnv* env, jthread thread, const void* data) {
-    return ERR(NOT_IMPLEMENTED);
+    return ThreadUtil::SetThreadLocalStorage(env, thread, data);
   }
 
   static jvmtiError GetThreadLocalStorage(jvmtiEnv* env, jthread thread, void** data_ptr) {
-    return ERR(NOT_IMPLEMENTED);
+    return ThreadUtil::GetThreadLocalStorage(env, thread, data_ptr);
   }
 
   static jvmtiError GetTopThreadGroups(jvmtiEnv* env,
@@ -593,7 +595,7 @@
                                            jclass klass,
                                            jint* minor_version_ptr,
                                            jint* major_version_ptr) {
-    return ERR(NOT_IMPLEMENTED);
+    return ClassUtil::GetClassVersionNumbers(env, klass, minor_version_ptr, major_version_ptr);
   }
 
   static jvmtiError GetConstantPool(jvmtiEnv* env,
@@ -631,7 +633,17 @@
   }
 
   static jvmtiError RetransformClasses(jvmtiEnv* env, jint class_count, const jclass* classes) {
-    return ERR(NOT_IMPLEMENTED);
+    std::string error_msg;
+    jvmtiError res = Transformer::RetransformClasses(ArtJvmTiEnv::AsArtJvmTiEnv(env),
+                                                     art::Runtime::Current(),
+                                                     art::Thread::Current(),
+                                                     class_count,
+                                                     classes,
+                                                     &error_msg);
+    if (res != OK) {
+      LOG(WARNING) << "FAILURE TO RETRANFORM " << error_msg;
+    }
+    return res;
   }
 
   static jvmtiError RedefineClasses(jvmtiEnv* env,
@@ -1089,11 +1101,12 @@
   }
 
   static jvmtiError GetPhase(jvmtiEnv* env, jvmtiPhase* phase_ptr) {
-    return ERR(NOT_IMPLEMENTED);
+    return PhaseUtil::GetPhase(env, phase_ptr);
   }
 
   static jvmtiError DisposeEnvironment(jvmtiEnv* env) {
     ENSURE_VALID_ENV(env);
+    gEventHandler.RemoveArtJvmTiEnv(ArtJvmTiEnv::AsArtJvmTiEnv(env));
     delete env;
     return OK;
   }
@@ -1255,78 +1268,6 @@
     *format_ptr = jvmtiJlocationFormat::JVMTI_JLOCATION_JVMBCI;
     return ERR(NONE);
   }
-
-  // TODO Remove this once events are working.
-  static jvmtiError RetransformClassWithHook(jvmtiEnv* env,
-                                             jclass klass,
-                                             jvmtiEventClassFileLoadHook hook) {
-    std::vector<jclass> classes;
-    classes.push_back(klass);
-    return RetransformClassesWithHook(reinterpret_cast<ArtJvmTiEnv*>(env), classes, hook);
-  }
-
-  // TODO This will be called by the event handler for the art::ti Event Load Event
-  static jvmtiError RetransformClassesWithHook(ArtJvmTiEnv* env,
-                                               const std::vector<jclass>& classes,
-                                               jvmtiEventClassFileLoadHook hook) {
-    if (!IsValidEnv(env)) {
-      return ERR(INVALID_ENVIRONMENT);
-    }
-    jvmtiError res = OK;
-    std::string error;
-    for (jclass klass : classes) {
-      JNIEnv* jni_env = nullptr;
-      jobject loader = nullptr;
-      std::string name;
-      jobject protection_domain = nullptr;
-      jint data_len = 0;
-      unsigned char* dex_data = nullptr;
-      jvmtiError ret = OK;
-      std::string location;
-      if ((ret = GetTransformationData(env,
-                                       klass,
-                                       /*out*/&location,
-                                       /*out*/&jni_env,
-                                       /*out*/&loader,
-                                       /*out*/&name,
-                                       /*out*/&protection_domain,
-                                       /*out*/&data_len,
-                                       /*out*/&dex_data)) != OK) {
-        // TODO Do something more here? Maybe give log statements?
-        return ret;
-      }
-      jint new_data_len = 0;
-      unsigned char* new_dex_data = nullptr;
-      hook(env,
-           jni_env,
-           klass,
-           loader,
-           name.c_str(),
-           protection_domain,
-           data_len,
-           dex_data,
-           /*out*/&new_data_len,
-           /*out*/&new_dex_data);
-      // Check if anything actually changed.
-      if ((new_data_len != 0 || new_dex_data != nullptr) && new_dex_data != dex_data) {
-        jvmtiClassDefinition def = { klass, new_data_len, new_dex_data };
-        res = Redefiner::RedefineClasses(env,
-                                         art::Runtime::Current(),
-                                         art::Thread::Current(),
-                                         1,
-                                         &def,
-                                         &error);
-        env->Deallocate(new_dex_data);
-      }
-      // Deallocate the old dex data.
-      env->Deallocate(dex_data);
-      if (res != OK) {
-        LOG(ERROR) << "FAILURE TO REDEFINE " << error;
-        return res;
-      }
-    }
-    return OK;
-  }
 };
 
 static bool IsJvmtiVersion(jint version) {
@@ -1362,17 +1303,35 @@
 // The plugin initialization function. This adds the jvmti environment.
 extern "C" bool ArtPlugin_Initialize() {
   art::Runtime* runtime = art::Runtime::Current();
+
+  if (runtime->IsStarted()) {
+    PhaseUtil::SetToLive();
+  } else {
+    PhaseUtil::SetToOnLoad();
+  }
+  PhaseUtil::Register(&gEventHandler);
+  ThreadUtil::Register(&gEventHandler);
+  ClassUtil::Register(&gEventHandler);
+  DumpUtil::Register(&gEventHandler);
+
   runtime->GetJavaVM()->AddEnvironmentHook(GetEnvHandler);
   runtime->AddSystemWeakHolder(&gObjectTagTable);
+
+  return true;
+}
+
+extern "C" bool ArtPlugin_Deinitialize() {
+  PhaseUtil::Unregister();
+  ThreadUtil::Unregister();
+  ClassUtil::Unregister();
+  DumpUtil::Unregister();
+
   return true;
 }
 
 // The actual struct holding all of the entrypoints into the jvmti interface.
 const jvmtiInterface_1 gJvmtiInterface = {
-  // SPECIAL FUNCTION: RetransformClassWithHook Is normally reserved1
-  // TODO Remove once we have events working.
-  reinterpret_cast<void*>(JvmtiFunctions::RetransformClassWithHook),
-  // nullptr,  // reserved1
+  nullptr,  // reserved1
   JvmtiFunctions::SetEventNotificationMode,
   nullptr,  // reserved3
   JvmtiFunctions::GetAllThreads,
diff --git a/runtime/openjdkjvmti/art_jvmti.h b/runtime/openjdkjvmti/art_jvmti.h
index 5eadc5a..256c3a6 100644
--- a/runtime/openjdkjvmti/art_jvmti.h
+++ b/runtime/openjdkjvmti/art_jvmti.h
@@ -36,6 +36,7 @@
 
 #include <jni.h>
 
+#include "base/array_slice.h"
 #include "base/casts.h"
 #include "base/logging.h"
 #include "base/macros.h"
@@ -47,6 +48,7 @@
 namespace openjdkjvmti {
 
 extern const jvmtiInterface_1 gJvmtiInterface;
+extern EventHandler gEventHandler;
 
 // A structure that is a jvmtiEnv with additional information for the runtime.
 struct ArtJvmTiEnv : public jvmtiEnv {
@@ -134,7 +136,7 @@
     .can_get_current_contended_monitor               = 0,
     .can_get_monitor_info                            = 0,
     .can_pop_frame                                   = 0,
-    .can_redefine_classes                            = 0,
+    .can_redefine_classes                            = 1,
     .can_signal_thread                               = 0,
     .can_get_source_file_name                        = 0,
     .can_get_line_numbers                            = 0,
@@ -162,7 +164,7 @@
     .can_get_owned_monitor_stack_depth_info          = 0,
     .can_get_constant_pool                           = 0,
     .can_set_native_method_prefix                    = 0,
-    .can_retransform_classes                         = 0,
+    .can_retransform_classes                         = 1,
     .can_retransform_any_class                       = 0,
     .can_generate_resource_exhaustion_heap_events    = 0,
     .can_generate_resource_exhaustion_threads_events = 0,
diff --git a/runtime/openjdkjvmti/events-inl.h b/runtime/openjdkjvmti/events-inl.h
index 1e07bc6..655a53a 100644
--- a/runtime/openjdkjvmti/events-inl.h
+++ b/runtime/openjdkjvmti/events-inl.h
@@ -37,91 +37,143 @@
   }
 }
 
-template <typename FnType>
-ALWAYS_INLINE static inline FnType* GetCallback(ArtJvmTiEnv* env, ArtJvmtiEvent event) {
-  if (env->event_callbacks == nullptr) {
-    return nullptr;
-  }
+namespace impl {
 
-  // TODO: Add a type check. Can be done, for example, by an explicitly instantiated template
-  //       function.
+// Infrastructure to achieve type safety for event dispatch.
 
-  switch (event) {
-    case ArtJvmtiEvent::kVmInit:
-      return reinterpret_cast<FnType*>(env->event_callbacks->VMInit);
-    case ArtJvmtiEvent::kVmDeath:
-      return reinterpret_cast<FnType*>(env->event_callbacks->VMDeath);
-    case ArtJvmtiEvent::kThreadStart:
-      return reinterpret_cast<FnType*>(env->event_callbacks->ThreadStart);
-    case ArtJvmtiEvent::kThreadEnd:
-      return reinterpret_cast<FnType*>(env->event_callbacks->ThreadEnd);
-    case ArtJvmtiEvent::kClassFileLoadHookRetransformable:
-    case ArtJvmtiEvent::kClassFileLoadHookNonRetransformable:
-      return reinterpret_cast<FnType*>(env->event_callbacks->ClassFileLoadHook);
-    case ArtJvmtiEvent::kClassLoad:
-      return reinterpret_cast<FnType*>(env->event_callbacks->ClassLoad);
-    case ArtJvmtiEvent::kClassPrepare:
-      return reinterpret_cast<FnType*>(env->event_callbacks->ClassPrepare);
-    case ArtJvmtiEvent::kVmStart:
-      return reinterpret_cast<FnType*>(env->event_callbacks->VMStart);
-    case ArtJvmtiEvent::kException:
-      return reinterpret_cast<FnType*>(env->event_callbacks->Exception);
-    case ArtJvmtiEvent::kExceptionCatch:
-      return reinterpret_cast<FnType*>(env->event_callbacks->ExceptionCatch);
-    case ArtJvmtiEvent::kSingleStep:
-      return reinterpret_cast<FnType*>(env->event_callbacks->SingleStep);
-    case ArtJvmtiEvent::kFramePop:
-      return reinterpret_cast<FnType*>(env->event_callbacks->FramePop);
-    case ArtJvmtiEvent::kBreakpoint:
-      return reinterpret_cast<FnType*>(env->event_callbacks->Breakpoint);
-    case ArtJvmtiEvent::kFieldAccess:
-      return reinterpret_cast<FnType*>(env->event_callbacks->FieldAccess);
-    case ArtJvmtiEvent::kFieldModification:
-      return reinterpret_cast<FnType*>(env->event_callbacks->FieldModification);
-    case ArtJvmtiEvent::kMethodEntry:
-      return reinterpret_cast<FnType*>(env->event_callbacks->MethodEntry);
-    case ArtJvmtiEvent::kMethodExit:
-      return reinterpret_cast<FnType*>(env->event_callbacks->MethodExit);
-    case ArtJvmtiEvent::kNativeMethodBind:
-      return reinterpret_cast<FnType*>(env->event_callbacks->NativeMethodBind);
-    case ArtJvmtiEvent::kCompiledMethodLoad:
-      return reinterpret_cast<FnType*>(env->event_callbacks->CompiledMethodLoad);
-    case ArtJvmtiEvent::kCompiledMethodUnload:
-      return reinterpret_cast<FnType*>(env->event_callbacks->CompiledMethodUnload);
-    case ArtJvmtiEvent::kDynamicCodeGenerated:
-      return reinterpret_cast<FnType*>(env->event_callbacks->DynamicCodeGenerated);
-    case ArtJvmtiEvent::kDataDumpRequest:
-      return reinterpret_cast<FnType*>(env->event_callbacks->DataDumpRequest);
-    case ArtJvmtiEvent::kMonitorWait:
-      return reinterpret_cast<FnType*>(env->event_callbacks->MonitorWait);
-    case ArtJvmtiEvent::kMonitorWaited:
-      return reinterpret_cast<FnType*>(env->event_callbacks->MonitorWaited);
-    case ArtJvmtiEvent::kMonitorContendedEnter:
-      return reinterpret_cast<FnType*>(env->event_callbacks->MonitorContendedEnter);
-    case ArtJvmtiEvent::kMonitorContendedEntered:
-      return reinterpret_cast<FnType*>(env->event_callbacks->MonitorContendedEntered);
-    case ArtJvmtiEvent::kResourceExhausted:
-      return reinterpret_cast<FnType*>(env->event_callbacks->ResourceExhausted);
-    case ArtJvmtiEvent::kGarbageCollectionStart:
-      return reinterpret_cast<FnType*>(env->event_callbacks->GarbageCollectionStart);
-    case ArtJvmtiEvent::kGarbageCollectionFinish:
-      return reinterpret_cast<FnType*>(env->event_callbacks->GarbageCollectionFinish);
-    case ArtJvmtiEvent::kObjectFree:
-      return reinterpret_cast<FnType*>(env->event_callbacks->ObjectFree);
-    case ArtJvmtiEvent::kVmObjectAlloc:
-      return reinterpret_cast<FnType*>(env->event_callbacks->VMObjectAlloc);
-  }
-  return nullptr;
+#define FORALL_EVENT_TYPES(fn)                                                       \
+  fn(VMInit,                  ArtJvmtiEvent::kVmInit)                                \
+  fn(VMDeath,                 ArtJvmtiEvent::kVmDeath)                               \
+  fn(ThreadStart,             ArtJvmtiEvent::kThreadStart)                           \
+  fn(ThreadEnd,               ArtJvmtiEvent::kThreadEnd)                             \
+  fn(ClassFileLoadHook,       ArtJvmtiEvent::kClassFileLoadHookRetransformable)      \
+  fn(ClassFileLoadHook,       ArtJvmtiEvent::kClassFileLoadHookNonRetransformable)   \
+  fn(ClassLoad,               ArtJvmtiEvent::kClassLoad)                             \
+  fn(ClassPrepare,            ArtJvmtiEvent::kClassPrepare)                          \
+  fn(VMStart,                 ArtJvmtiEvent::kVmStart)                               \
+  fn(Exception,               ArtJvmtiEvent::kException)                             \
+  fn(ExceptionCatch,          ArtJvmtiEvent::kExceptionCatch)                        \
+  fn(SingleStep,              ArtJvmtiEvent::kSingleStep)                            \
+  fn(FramePop,                ArtJvmtiEvent::kFramePop)                              \
+  fn(Breakpoint,              ArtJvmtiEvent::kBreakpoint)                            \
+  fn(FieldAccess,             ArtJvmtiEvent::kFieldAccess)                           \
+  fn(FieldModification,       ArtJvmtiEvent::kFieldModification)                     \
+  fn(MethodEntry,             ArtJvmtiEvent::kMethodEntry)                           \
+  fn(MethodExit,              ArtJvmtiEvent::kMethodExit)                            \
+  fn(NativeMethodBind,        ArtJvmtiEvent::kNativeMethodBind)                      \
+  fn(CompiledMethodLoad,      ArtJvmtiEvent::kCompiledMethodLoad)                    \
+  fn(CompiledMethodUnload,    ArtJvmtiEvent::kCompiledMethodUnload)                  \
+  fn(DynamicCodeGenerated,    ArtJvmtiEvent::kDynamicCodeGenerated)                  \
+  fn(DataDumpRequest,         ArtJvmtiEvent::kDataDumpRequest)                       \
+  fn(MonitorWait,             ArtJvmtiEvent::kMonitorWait)                           \
+  fn(MonitorWaited,           ArtJvmtiEvent::kMonitorWaited)                         \
+  fn(MonitorContendedEnter,   ArtJvmtiEvent::kMonitorContendedEnter)                 \
+  fn(MonitorContendedEntered, ArtJvmtiEvent::kMonitorContendedEntered)               \
+  fn(ResourceExhausted,       ArtJvmtiEvent::kResourceExhausted)                     \
+  fn(GarbageCollectionStart,  ArtJvmtiEvent::kGarbageCollectionStart)                \
+  fn(GarbageCollectionFinish, ArtJvmtiEvent::kGarbageCollectionFinish)               \
+  fn(ObjectFree,              ArtJvmtiEvent::kObjectFree)                            \
+  fn(VMObjectAlloc,           ArtJvmtiEvent::kVmObjectAlloc)
+
+template <ArtJvmtiEvent kEvent>
+struct EventFnType {
+};
+
+#define EVENT_FN_TYPE(name, enum_name)               \
+template <>                                          \
+struct EventFnType<enum_name> {                      \
+  using type = decltype(jvmtiEventCallbacks().name); \
+};
+
+FORALL_EVENT_TYPES(EVENT_FN_TYPE)
+
+#undef EVENT_FN_TYPE
+
+template <ArtJvmtiEvent kEvent>
+ALWAYS_INLINE inline typename EventFnType<kEvent>::type GetCallback(ArtJvmTiEnv* env);
+
+#define GET_CALLBACK(name, enum_name)                                     \
+template <>                                                               \
+ALWAYS_INLINE inline EventFnType<enum_name>::type GetCallback<enum_name>( \
+    ArtJvmTiEnv* env) {                                                   \
+  if (env->event_callbacks == nullptr) {                                  \
+    return nullptr;                                                       \
+  }                                                                       \
+  return env->event_callbacks->name;                                      \
 }
 
-template <typename ...Args>
+FORALL_EVENT_TYPES(GET_CALLBACK)
+
+#undef GET_CALLBACK
+
+#undef FORALL_EVENT_TYPES
+
+}  // namespace impl
+
+// C++ does not allow partial template function specialization. The dispatch for our separated
+// ClassFileLoadHook event types is the same, so use this helper for code deduplication.
+// TODO Locking of some type!
+template <ArtJvmtiEvent kEvent>
+inline void EventHandler::DispatchClassFileLoadHookEvent(art::Thread* thread,
+                                                         JNIEnv* jnienv,
+                                                         jclass class_being_redefined,
+                                                         jobject loader,
+                                                         const char* name,
+                                                         jobject protection_domain,
+                                                         jint class_data_len,
+                                                         const unsigned char* class_data,
+                                                         jint* new_class_data_len,
+                                                         unsigned char** new_class_data) const {
+  static_assert(kEvent == ArtJvmtiEvent::kClassFileLoadHookRetransformable ||
+                kEvent == ArtJvmtiEvent::kClassFileLoadHookNonRetransformable, "Unsupported event");
+  jint current_len = class_data_len;
+  unsigned char* current_class_data = const_cast<unsigned char*>(class_data);
+  ArtJvmTiEnv* last_env = nullptr;
+  for (ArtJvmTiEnv* env : envs) {
+    if (ShouldDispatch<kEvent>(env, thread)) {
+      jint new_len;
+      unsigned char* new_data;
+      auto callback = impl::GetCallback<kEvent>(env);
+      callback(env,
+               jnienv,
+               class_being_redefined,
+               loader,
+               name,
+               protection_domain,
+               current_len,
+               current_class_data,
+               &new_len,
+               &new_data);
+      if (new_data != nullptr && new_data != current_class_data) {
+        // Destroy the data the last transformer made. We skip this if the previous state was the
+        // initial one since we don't know here which jvmtiEnv allocated it.
+        // NB Currently this doesn't matter since all allocations just go to malloc but in the
+        // future we might have jvmtiEnv's keep track of their allocations for leak-checking.
+        if (last_env != nullptr) {
+          last_env->Deallocate(current_class_data);
+        }
+        last_env = env;
+        current_class_data = new_data;
+        current_len = new_len;
+      }
+    }
+  }
+  if (last_env != nullptr) {
+    *new_class_data_len = current_len;
+    *new_class_data = current_class_data;
+  }
+}
+
+// Our goal for DispatchEvent: Do not allow implicit type conversion. Types of ...args must match
+// exactly the argument types of the corresponding Jvmti kEvent function pointer.
+
+template <ArtJvmtiEvent kEvent, typename ...Args>
 inline void EventHandler::DispatchEvent(art::Thread* thread,
-                                        ArtJvmtiEvent event,
                                         Args... args) const {
   using FnType = void(jvmtiEnv*, Args...);
   for (ArtJvmTiEnv* env : envs) {
-    if (ShouldDispatch(event, env, thread)) {
-      FnType* callback = GetCallback<FnType>(env, event);
+    if (ShouldDispatch<kEvent>(env, thread)) {
+      FnType* callback = impl::GetCallback<kEvent>(env);
       if (callback != nullptr) {
         (*callback)(env, args...);
       }
@@ -129,14 +181,66 @@
   }
 }
 
-inline bool EventHandler::ShouldDispatch(ArtJvmtiEvent event,
-                                         ArtJvmTiEnv* env,
-                                         art::Thread* thread) {
-  bool dispatch = env->event_masks.global_event_mask.Test(event);
+// C++ does not allow partial template function specialization. The dispatch for our separated
+// ClassFileLoadHook event types is the same, and in the DispatchClassFileLoadHookEvent helper.
+// The following two DispatchEvent specializations dispatch to it.
+template <>
+inline void EventHandler::DispatchEvent<ArtJvmtiEvent::kClassFileLoadHookRetransformable>(
+    art::Thread* thread,
+    JNIEnv* jnienv,
+    jclass class_being_redefined,
+    jobject loader,
+    const char* name,
+    jobject protection_domain,
+    jint class_data_len,
+    const unsigned char* class_data,
+    jint* new_class_data_len,
+    unsigned char** new_class_data) const {
+  return DispatchClassFileLoadHookEvent<ArtJvmtiEvent::kClassFileLoadHookRetransformable>(
+      thread,
+      jnienv,
+      class_being_redefined,
+      loader,
+      name,
+      protection_domain,
+      class_data_len,
+      class_data,
+      new_class_data_len,
+      new_class_data);
+}
+template <>
+inline void EventHandler::DispatchEvent<ArtJvmtiEvent::kClassFileLoadHookNonRetransformable>(
+    art::Thread* thread,
+    JNIEnv* jnienv,
+    jclass class_being_redefined,
+    jobject loader,
+    const char* name,
+    jobject protection_domain,
+    jint class_data_len,
+    const unsigned char* class_data,
+    jint* new_class_data_len,
+    unsigned char** new_class_data) const {
+  return DispatchClassFileLoadHookEvent<ArtJvmtiEvent::kClassFileLoadHookNonRetransformable>(
+      thread,
+      jnienv,
+      class_being_redefined,
+      loader,
+      name,
+      protection_domain,
+      class_data_len,
+      class_data,
+      new_class_data_len,
+      new_class_data);
+}
 
-  if (!dispatch && thread != nullptr && env->event_masks.unioned_thread_event_mask.Test(event)) {
+template <ArtJvmtiEvent kEvent>
+inline bool EventHandler::ShouldDispatch(ArtJvmTiEnv* env,
+                                         art::Thread* thread) {
+  bool dispatch = env->event_masks.global_event_mask.Test(kEvent);
+
+  if (!dispatch && thread != nullptr && env->event_masks.unioned_thread_event_mask.Test(kEvent)) {
     EventMask* mask = env->event_masks.GetEventMaskOrNull(thread);
-    dispatch = mask != nullptr && mask->Test(event);
+    dispatch = mask != nullptr && mask->Test(kEvent);
   }
   return dispatch;
 }
diff --git a/runtime/openjdkjvmti/events.cc b/runtime/openjdkjvmti/events.cc
index f38aa86..1da08a0 100644
--- a/runtime/openjdkjvmti/events.cc
+++ b/runtime/openjdkjvmti/events.cc
@@ -144,6 +144,18 @@
   envs.push_back(env);
 }
 
+void EventHandler::RemoveArtJvmTiEnv(ArtJvmTiEnv* env) {
+  auto it = std::find(envs.begin(), envs.end(), env);
+  if (it != envs.end()) {
+    envs.erase(it);
+    for (size_t i = static_cast<size_t>(ArtJvmtiEvent::kMinEventTypeVal);
+         i <= static_cast<size_t>(ArtJvmtiEvent::kMaxEventTypeVal);
+         ++i) {
+      RecalculateGlobalEventMask(static_cast<ArtJvmtiEvent>(i));
+    }
+  }
+}
+
 static bool IsThreadControllable(ArtJvmtiEvent event) {
   switch (event) {
     case ArtJvmtiEvent::kVmInit:
@@ -194,13 +206,12 @@
       ScopedLocalRef<jclass> klass(
           jni_env, jni_env->AddLocalReference<jclass>(obj->Ptr()->GetClass()));
 
-      handler_->DispatchEvent(self,
-                              ArtJvmtiEvent::kVmObjectAlloc,
-                              jni_env,
-                              thread.get(),
-                              object.get(),
-                              klass.get(),
-                              byte_count);
+      handler_->DispatchEvent<ArtJvmtiEvent::kVmObjectAlloc>(self,
+                                                             reinterpret_cast<JNIEnv*>(jni_env),
+                                                             thread.get(),
+                                                             object.get(),
+                                                             klass.get(),
+                                                             static_cast<jlong>(byte_count));
     }
   }
 
@@ -229,11 +240,11 @@
         finish_enabled_(false) {}
 
   void StartPause() OVERRIDE {
-    handler_->DispatchEvent(nullptr, ArtJvmtiEvent::kGarbageCollectionStart);
+    handler_->DispatchEvent<ArtJvmtiEvent::kGarbageCollectionStart>(nullptr);
   }
 
   void EndPause() OVERRIDE {
-    handler_->DispatchEvent(nullptr, ArtJvmtiEvent::kGarbageCollectionFinish);
+    handler_->DispatchEvent<ArtJvmtiEvent::kGarbageCollectionFinish>(nullptr);
   }
 
   bool IsEnabled() {
diff --git a/runtime/openjdkjvmti/events.h b/runtime/openjdkjvmti/events.h
index 7990141..4e20d17 100644
--- a/runtime/openjdkjvmti/events.h
+++ b/runtime/openjdkjvmti/events.h
@@ -141,6 +141,9 @@
   // enabled, yet.
   void RegisterArtJvmTiEnv(ArtJvmTiEnv* env);
 
+  // Remove an env.
+  void RemoveArtJvmTiEnv(ArtJvmTiEnv* env);
+
   bool IsEventEnabledAnywhere(ArtJvmtiEvent event) const {
     if (!EventMask::EventIsInRange(event)) {
       return false;
@@ -153,9 +156,9 @@
                       ArtJvmtiEvent event,
                       jvmtiEventMode mode);
 
-  template <typename ...Args>
+  template <ArtJvmtiEvent kEvent, typename ...Args>
   ALWAYS_INLINE
-  inline void DispatchEvent(art::Thread* thread, ArtJvmtiEvent event, Args... args) const;
+  inline void DispatchEvent(art::Thread* thread, Args... args) const;
 
   // Tell the event handler capabilities were added/lost so it can adjust the sent events.If
   // caps_added is true then caps is all the newly set capabilities of the jvmtiEnv. If it is false
@@ -166,8 +169,9 @@
                                         bool added);
 
  private:
+  template <ArtJvmtiEvent kEvent>
   ALWAYS_INLINE
-  static inline bool ShouldDispatch(ArtJvmtiEvent event, ArtJvmTiEnv* env, art::Thread* thread);
+  static inline bool ShouldDispatch(ArtJvmTiEnv* env, art::Thread* thread);
 
   ALWAYS_INLINE
   inline bool NeedsEventUpdate(ArtJvmTiEnv* env,
@@ -178,6 +182,18 @@
   ALWAYS_INLINE
   inline void RecalculateGlobalEventMask(ArtJvmtiEvent event);
 
+  template <ArtJvmtiEvent kEvent>
+  ALWAYS_INLINE inline void DispatchClassFileLoadHookEvent(art::Thread* thread,
+                                                           JNIEnv* jnienv,
+                                                           jclass class_being_redefined,
+                                                           jobject loader,
+                                                           const char* name,
+                                                           jobject protection_domain,
+                                                           jint class_data_len,
+                                                           const unsigned char* class_data,
+                                                           jint* new_class_data_len,
+                                                           unsigned char** new_class_data) const;
+
   void HandleEventType(ArtJvmtiEvent event, bool enable);
 
   // List of all JvmTiEnv objects that have been created, in their creation order.
diff --git a/runtime/openjdkjvmti/object_tagging.cc b/runtime/openjdkjvmti/object_tagging.cc
index 94cb46a..b27c2a3 100644
--- a/runtime/openjdkjvmti/object_tagging.cc
+++ b/runtime/openjdkjvmti/object_tagging.cc
@@ -207,7 +207,7 @@
 }
 
 void ObjectTagTable::HandleNullSweep(jlong tag) {
-  event_handler_->DispatchEvent(nullptr, ArtJvmtiEvent::kObjectFree, tag);
+  event_handler_->DispatchEvent<ArtJvmtiEvent::kObjectFree>(nullptr, tag);
 }
 
 template <typename T, ObjectTagTable::TableUpdateNullTarget kTargetNull>
diff --git a/runtime/openjdkjvmti/ti_class.cc b/runtime/openjdkjvmti/ti_class.cc
index d1324bc..6dbab86 100644
--- a/runtime/openjdkjvmti/ti_class.cc
+++ b/runtime/openjdkjvmti/ti_class.cc
@@ -31,16 +31,119 @@
 
 #include "ti_class.h"
 
+#include <mutex>
+#include <unordered_set>
+
 #include "art_jvmti.h"
+#include "base/macros.h"
 #include "class_table-inl.h"
 #include "class_linker.h"
+#include "events-inl.h"
+#include "handle.h"
+#include "jni_env_ext-inl.h"
 #include "jni_internal.h"
 #include "runtime.h"
+#include "runtime_callbacks.h"
+#include "ScopedLocalRef.h"
 #include "scoped_thread_state_change-inl.h"
 #include "thread-inl.h"
+#include "thread_list.h"
 
 namespace openjdkjvmti {
 
+struct ClassCallback : public art::ClassLoadCallback {
+  void ClassLoad(art::Handle<art::mirror::Class> klass) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    if (event_handler->IsEventEnabledAnywhere(ArtJvmtiEvent::kClassLoad)) {
+      art::Thread* thread = art::Thread::Current();
+      ScopedLocalRef<jclass> jklass(thread->GetJniEnv(),
+                                    thread->GetJniEnv()->AddLocalReference<jclass>(klass.Get()));
+      ScopedLocalRef<jthread> thread_jni(
+          thread->GetJniEnv(), thread->GetJniEnv()->AddLocalReference<jthread>(thread->GetPeer()));
+      {
+        art::ScopedThreadSuspension sts(thread, art::ThreadState::kNative);
+        event_handler->DispatchEvent<ArtJvmtiEvent::kClassLoad>(
+            thread,
+            static_cast<JNIEnv*>(thread->GetJniEnv()),
+            thread_jni.get(),
+            jklass.get());
+      }
+      AddTempClass(thread, jklass.get());
+    }
+  }
+
+  void ClassPrepare(art::Handle<art::mirror::Class> temp_klass ATTRIBUTE_UNUSED,
+                    art::Handle<art::mirror::Class> klass)
+      REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    if (event_handler->IsEventEnabledAnywhere(ArtJvmtiEvent::kClassPrepare)) {
+      art::Thread* thread = art::Thread::Current();
+      ScopedLocalRef<jclass> jklass(thread->GetJniEnv(),
+                                    thread->GetJniEnv()->AddLocalReference<jclass>(klass.Get()));
+      ScopedLocalRef<jthread> thread_jni(
+          thread->GetJniEnv(), thread->GetJniEnv()->AddLocalReference<jthread>(thread->GetPeer()));
+      art::ScopedThreadSuspension sts(thread, art::ThreadState::kNative);
+      event_handler->DispatchEvent<ArtJvmtiEvent::kClassPrepare>(
+          thread,
+          static_cast<JNIEnv*>(thread->GetJniEnv()),
+          thread_jni.get(),
+          jklass.get());
+    }
+  }
+
+  void AddTempClass(art::Thread* self, jclass klass) {
+    std::unique_lock<std::mutex> mu(temp_classes_lock);
+    temp_classes.push_back(reinterpret_cast<jclass>(self->GetJniEnv()->NewGlobalRef(klass)));
+  }
+
+  void HandleTempClass(art::Handle<art::mirror::Class> temp_klass,
+                       art::Handle<art::mirror::Class> klass)
+      REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    std::unique_lock<std::mutex> mu(temp_classes_lock);
+    if (temp_classes.empty()) {
+      return;
+    }
+
+    art::Thread* self = art::Thread::Current();
+    for (auto it = temp_classes.begin(); it != temp_classes.end(); ++it) {
+      if (temp_klass.Get() == art::ObjPtr<art::mirror::Class>::DownCast(self->DecodeJObject(*it))) {
+        temp_classes.erase(it);
+        FixupTempClass(temp_klass, klass);
+      }
+    }
+  }
+
+  void FixupTempClass(art::Handle<art::mirror::Class> temp_klass ATTRIBUTE_UNUSED,
+                      art::Handle<art::mirror::Class> klass ATTRIBUTE_UNUSED)
+     REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    // TODO: Implement.
+  }
+
+  // A set of all the temp classes we have handed out. We have to fix up references to these.
+  // For simplicity, we store the temp classes as JNI global references in a vector. Normally a
+  // Prepare event will closely follow, so the vector should be small.
+  std::mutex temp_classes_lock;
+  std::vector<jclass> temp_classes;
+
+  EventHandler* event_handler = nullptr;
+};
+
+ClassCallback gClassCallback;
+
+void ClassUtil::Register(EventHandler* handler) {
+  gClassCallback.event_handler = handler;
+  art::ScopedThreadStateChange stsc(art::Thread::Current(),
+                                    art::ThreadState::kWaitingForDebuggerToAttach);
+  art::ScopedSuspendAll ssa("Add load callback");
+  art::Runtime::Current()->GetRuntimeCallbacks()->AddClassLoadCallback(&gClassCallback);
+}
+
+void ClassUtil::Unregister() {
+  art::ScopedThreadStateChange stsc(art::Thread::Current(),
+                                    art::ThreadState::kWaitingForDebuggerToAttach);
+  art::ScopedSuspendAll ssa("Remove thread callback");
+  art::Runtime* runtime = art::Runtime::Current();
+  runtime->GetRuntimeCallbacks()->RemoveClassLoadCallback(&gClassCallback);
+}
+
 jvmtiError ClassUtil::GetClassFields(jvmtiEnv* env,
                                      jclass jklass,
                                      jint* field_count_ptr,
@@ -200,7 +303,9 @@
   }
 
   // TODO: Support generic signature.
-  *generic_ptr = nullptr;
+  if (generic_ptr != nullptr) {
+    *generic_ptr = nullptr;
+  }
 
   // Everything is fine, release the buffers.
   sig_copy.release();
@@ -417,4 +522,35 @@
   return ERR(NONE);
 }
 
+jvmtiError ClassUtil::GetClassVersionNumbers(jvmtiEnv* env ATTRIBUTE_UNUSED,
+                                             jclass jklass,
+                                             jint* minor_version_ptr,
+                                             jint* major_version_ptr) {
+  art::ScopedObjectAccess soa(art::Thread::Current());
+  if (jklass == nullptr) {
+    return ERR(INVALID_CLASS);
+  }
+  art::ObjPtr<art::mirror::Object> jklass_obj = soa.Decode<art::mirror::Object>(jklass);
+  if (!jklass_obj->IsClass()) {
+    return ERR(INVALID_CLASS);
+  }
+  art::ObjPtr<art::mirror::Class> klass = jklass_obj->AsClass();
+  if (klass->IsPrimitive() || klass->IsArrayClass()) {
+    return ERR(INVALID_CLASS);
+  }
+
+  if (minor_version_ptr == nullptr || major_version_ptr == nullptr) {
+    return ERR(NULL_POINTER);
+  }
+
+  // Note: proxies will show the dex file version of java.lang.reflect.Proxy, as that is
+  //       what their dex cache copies from.
+  uint32_t version = klass->GetDexFile().GetHeader().GetVersion();
+
+  *major_version_ptr = static_cast<jint>(version);
+  *minor_version_ptr = 0;
+
+  return ERR(NONE);
+}
+
 }  // namespace openjdkjvmti
diff --git a/runtime/openjdkjvmti/ti_class.h b/runtime/openjdkjvmti/ti_class.h
index 7a0fafb..aa2260f 100644
--- a/runtime/openjdkjvmti/ti_class.h
+++ b/runtime/openjdkjvmti/ti_class.h
@@ -37,8 +37,13 @@
 
 namespace openjdkjvmti {
 
+class EventHandler;
+
 class ClassUtil {
  public:
+  static void Register(EventHandler* event_handler);
+  static void Unregister();
+
   static jvmtiError GetClassFields(jvmtiEnv* env,
                                    jclass klass,
                                    jint* field_count_ptr,
@@ -72,6 +77,11 @@
 
   static jvmtiError IsInterface(jvmtiEnv* env, jclass klass, jboolean* is_interface_ptr);
   static jvmtiError IsArrayClass(jvmtiEnv* env, jclass klass, jboolean* is_array_class_ptr);
+
+  static jvmtiError GetClassVersionNumbers(jvmtiEnv* env,
+                                           jclass klass,
+                                           jint* minor_version_ptr,
+                                           jint* major_version_ptr);
 };
 
 }  // namespace openjdkjvmti
diff --git a/runtime/openjdkjvmti/ti_class_definition.cc b/runtime/openjdkjvmti/ti_class_definition.cc
new file mode 100644
index 0000000..2c2a79b
--- /dev/null
+++ b/runtime/openjdkjvmti/ti_class_definition.cc
@@ -0,0 +1,55 @@
+/* Copyright (C) 2016 The Android Open Source Project
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This file implements interfaces from the file jvmti.h. This implementation
+ * is licensed under the same terms as the file jvmti.h.  The
+ * copyright and license information for the file jvmti.h follows.
+ *
+ * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include "ti_class_definition.h"
+
+#include "dex_file.h"
+#include "handle_scope-inl.h"
+#include "handle.h"
+#include "mirror/class-inl.h"
+#include "mirror/object-inl.h"
+#include "thread.h"
+
+namespace openjdkjvmti {
+
+bool ArtClassDefinition::IsModified(art::Thread* self) const {
+  if (modified) {
+    return true;
+  }
+  // Check if the dex file we want to set is the same as the current one.
+  art::StackHandleScope<1> hs(self);
+  art::Handle<art::mirror::Class> h_klass(hs.NewHandle(self->DecodeJObject(klass)->AsClass()));
+  const art::DexFile& cur_dex_file = h_klass->GetDexFile();
+  return static_cast<jint>(cur_dex_file.Size()) != dex_len ||
+      memcmp(cur_dex_file.Begin(), dex_data.get(), dex_len) != 0;
+}
+
+}  // namespace openjdkjvmti
diff --git a/runtime/openjdkjvmti/ti_class_definition.h b/runtime/openjdkjvmti/ti_class_definition.h
new file mode 100644
index 0000000..dbe5da2
--- /dev/null
+++ b/runtime/openjdkjvmti/ti_class_definition.h
@@ -0,0 +1,77 @@
+/* Copyright (C) 2017 The Android Open Source Project
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This file implements interfaces from the file jvmti.h. This implementation
+ * is licensed under the same terms as the file jvmti.h.  The
+ * copyright and license information for the file jvmti.h follows.
+ *
+ * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef ART_RUNTIME_OPENJDKJVMTI_TI_CLASS_DEFINITION_H_
+#define ART_RUNTIME_OPENJDKJVMTI_TI_CLASS_DEFINITION_H_
+
+#include "art_jvmti.h"
+
+namespace openjdkjvmti {
+
+// A struct that stores data needed for redefining/transforming classes. This structure should only
+// even be accessed from a single thread and must not survive past the completion of the
+// redefinition/retransformation function that created it.
+struct ArtClassDefinition {
+ public:
+  jclass klass;
+  jobject loader;
+  std::string name;
+  jobject protection_domain;
+  jint dex_len;
+  JvmtiUniquePtr dex_data;
+  art::ArraySlice<const unsigned char> original_dex_file;
+
+  ArtClassDefinition() = default;
+  ArtClassDefinition(ArtClassDefinition&& o) = default;
+
+  void SetNewDexData(ArtJvmTiEnv* env, jint new_dex_len, unsigned char* new_dex_data) {
+    if (new_dex_data == nullptr) {
+      return;
+    } else if (new_dex_data != dex_data.get() || new_dex_len != dex_len) {
+      SetModified();
+      dex_len = new_dex_len;
+      dex_data = MakeJvmtiUniquePtr(env, new_dex_data);
+    }
+  }
+
+  void SetModified() {
+    modified = true;
+  }
+
+  bool IsModified(art::Thread* self) const REQUIRES_SHARED(art::Locks::mutator_lock_);
+
+ private:
+  bool modified;
+};
+
+}  // namespace openjdkjvmti
+
+#endif  // ART_RUNTIME_OPENJDKJVMTI_TI_CLASS_DEFINITION_H_
diff --git a/runtime/openjdkjvmti/ti_dump.cc b/runtime/openjdkjvmti/ti_dump.cc
new file mode 100644
index 0000000..d9e3ef1
--- /dev/null
+++ b/runtime/openjdkjvmti/ti_dump.cc
@@ -0,0 +1,74 @@
+/* Copyright (C) 2017 The Android Open Source Project
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This file implements interfaces from the file jvmti.h. This implementation
+ * is licensed under the same terms as the file jvmti.h.  The
+ * copyright and license information for the file jvmti.h follows.
+ *
+ * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include "ti_dump.h"
+
+#include <limits>
+
+
+#include "art_jvmti.h"
+#include "base/mutex.h"
+#include "events-inl.h"
+#include "runtime_callbacks.h"
+#include "scoped_thread_state_change-inl.h"
+#include "thread-inl.h"
+#include "thread_list.h"
+
+namespace openjdkjvmti {
+
+struct DumpCallback : public art::RuntimeSigQuitCallback {
+  void SigQuit() OVERRIDE REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    art::Thread* thread = art::Thread::Current();
+    art::ScopedThreadSuspension sts(thread, art::ThreadState::kNative);
+    event_handler->DispatchEvent<ArtJvmtiEvent::kDataDumpRequest>(nullptr);
+  }
+
+  EventHandler* event_handler = nullptr;
+};
+
+static DumpCallback gDumpCallback;
+
+void DumpUtil::Register(EventHandler* handler) {
+  gDumpCallback.event_handler = handler;
+  art::ScopedThreadStateChange stsc(art::Thread::Current(),
+                                    art::ThreadState::kWaitingForDebuggerToAttach);
+  art::ScopedSuspendAll ssa("Add sigquit callback");
+  art::Runtime::Current()->GetRuntimeCallbacks()->AddRuntimeSigQuitCallback(&gDumpCallback);
+}
+
+void DumpUtil::Unregister() {
+  art::ScopedThreadStateChange stsc(art::Thread::Current(),
+                                    art::ThreadState::kWaitingForDebuggerToAttach);
+  art::ScopedSuspendAll ssa("Remove sigquit callback");
+  art::Runtime::Current()->GetRuntimeCallbacks()->RemoveRuntimeSigQuitCallback(&gDumpCallback);
+}
+
+}  // namespace openjdkjvmti
diff --git a/runtime/openjdkjvmti/ti_dump.h b/runtime/openjdkjvmti/ti_dump.h
new file mode 100644
index 0000000..67cb239
--- /dev/null
+++ b/runtime/openjdkjvmti/ti_dump.h
@@ -0,0 +1,50 @@
+/* Copyright (C) 2017 The Android Open Source Project
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This file implements interfaces from the file jvmti.h. This implementation
+ * is licensed under the same terms as the file jvmti.h.  The
+ * copyright and license information for the file jvmti.h follows.
+ *
+ * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef ART_RUNTIME_OPENJDKJVMTI_TI_DUMP_H_
+#define ART_RUNTIME_OPENJDKJVMTI_TI_DUMP_H_
+
+#include "jni.h"
+#include "jvmti.h"
+
+namespace openjdkjvmti {
+
+class EventHandler;
+
+class DumpUtil {
+ public:
+  static void Register(EventHandler* event_handler);
+  static void Unregister();
+};
+
+}  // namespace openjdkjvmti
+
+#endif  // ART_RUNTIME_OPENJDKJVMTI_TI_DUMP_H_
diff --git a/runtime/openjdkjvmti/ti_phase.cc b/runtime/openjdkjvmti/ti_phase.cc
new file mode 100644
index 0000000..4970288
--- /dev/null
+++ b/runtime/openjdkjvmti/ti_phase.cc
@@ -0,0 +1,139 @@
+/* Copyright (C) 2017 The Android Open Source Project
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This file implements interfaces from the file jvmti.h. This implementation
+ * is licensed under the same terms as the file jvmti.h.  The
+ * copyright and license information for the file jvmti.h follows.
+ *
+ * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include "ti_phase.h"
+
+#include "art_jvmti.h"
+#include "base/macros.h"
+#include "events-inl.h"
+#include "runtime.h"
+#include "runtime_callbacks.h"
+#include "ScopedLocalRef.h"
+#include "scoped_thread_state_change-inl.h"
+#include "thread-inl.h"
+#include "thread_list.h"
+
+namespace openjdkjvmti {
+
+jvmtiPhase PhaseUtil::current_phase_ = static_cast<jvmtiPhase>(0);
+
+struct PhaseUtil::PhaseCallback : public art::RuntimePhaseCallback {
+  inline static JNIEnv* GetJniEnv() {
+    return reinterpret_cast<JNIEnv*>(art::Thread::Current()->GetJniEnv());
+  }
+
+  inline static jthread GetCurrentJThread() {
+    art::ScopedObjectAccess soa(art::Thread::Current());
+    return soa.AddLocalReference<jthread>(soa.Self()->GetPeer());
+  }
+
+  void NextRuntimePhase(RuntimePhase phase) REQUIRES_SHARED(art::Locks::mutator_lock_) OVERRIDE {
+    // TODO: Events.
+    switch (phase) {
+      case RuntimePhase::kInitialAgents:
+        PhaseUtil::current_phase_ = JVMTI_PHASE_PRIMORDIAL;
+        break;
+      case RuntimePhase::kStart:
+        {
+          art::ScopedThreadSuspension sts(art::Thread::Current(), art::ThreadState::kNative);
+          event_handler->DispatchEvent<ArtJvmtiEvent::kVmStart>(nullptr, GetJniEnv());
+          PhaseUtil::current_phase_ = JVMTI_PHASE_START;
+        }
+        break;
+      case RuntimePhase::kInit:
+        {
+          ScopedLocalRef<jthread> thread(GetJniEnv(), GetCurrentJThread());
+          art::ScopedThreadSuspension sts(art::Thread::Current(), art::ThreadState::kNative);
+          event_handler->DispatchEvent<ArtJvmtiEvent::kVmInit>(nullptr, GetJniEnv(), thread.get());
+          PhaseUtil::current_phase_ = JVMTI_PHASE_LIVE;
+        }
+        break;
+      case RuntimePhase::kDeath:
+        {
+          art::ScopedThreadSuspension sts(art::Thread::Current(), art::ThreadState::kNative);
+          event_handler->DispatchEvent<ArtJvmtiEvent::kVmDeath>(nullptr, GetJniEnv());
+          PhaseUtil::current_phase_ = JVMTI_PHASE_DEAD;
+        }
+        // TODO: Block events now.
+        break;
+    }
+  }
+
+  EventHandler* event_handler = nullptr;
+};
+
+PhaseUtil::PhaseCallback gPhaseCallback;
+
+jvmtiError PhaseUtil::GetPhase(jvmtiEnv* env ATTRIBUTE_UNUSED, jvmtiPhase* phase_ptr) {
+  if (phase_ptr == nullptr) {
+    return ERR(NULL_POINTER);
+  }
+  jvmtiPhase now = PhaseUtil::current_phase_;
+  DCHECK(now == JVMTI_PHASE_ONLOAD ||
+         now == JVMTI_PHASE_PRIMORDIAL ||
+         now == JVMTI_PHASE_START ||
+         now == JVMTI_PHASE_LIVE ||
+         now == JVMTI_PHASE_DEAD);
+  *phase_ptr = now;
+  return ERR(NONE);
+}
+
+void PhaseUtil::SetToOnLoad() {
+  DCHECK_EQ(0u, static_cast<size_t>(PhaseUtil::current_phase_));
+  PhaseUtil::current_phase_ = JVMTI_PHASE_ONLOAD;
+}
+
+void PhaseUtil::SetToPrimordial() {
+  DCHECK_EQ(static_cast<size_t>(JVMTI_PHASE_ONLOAD), static_cast<size_t>(PhaseUtil::current_phase_));
+  PhaseUtil::current_phase_ = JVMTI_PHASE_ONLOAD;
+}
+
+void PhaseUtil::SetToLive() {
+  DCHECK_EQ(static_cast<size_t>(0), static_cast<size_t>(PhaseUtil::current_phase_));
+  PhaseUtil::current_phase_ = JVMTI_PHASE_LIVE;
+}
+
+void PhaseUtil::Register(EventHandler* handler) {
+  gPhaseCallback.event_handler = handler;
+  art::ScopedThreadStateChange stsc(art::Thread::Current(),
+                                    art::ThreadState::kWaitingForDebuggerToAttach);
+  art::ScopedSuspendAll ssa("Add phase callback");
+  art::Runtime::Current()->GetRuntimeCallbacks()->AddRuntimePhaseCallback(&gPhaseCallback);
+}
+
+void PhaseUtil::Unregister() {
+  art::ScopedThreadStateChange stsc(art::Thread::Current(),
+                                    art::ThreadState::kWaitingForDebuggerToAttach);
+  art::ScopedSuspendAll ssa("Remove phase callback");
+  art::Runtime::Current()->GetRuntimeCallbacks()->RemoveRuntimePhaseCallback(&gPhaseCallback);
+}
+
+}  // namespace openjdkjvmti
diff --git a/runtime/openjdkjvmti/ti_phase.h b/runtime/openjdkjvmti/ti_phase.h
new file mode 100644
index 0000000..bd15fa6
--- /dev/null
+++ b/runtime/openjdkjvmti/ti_phase.h
@@ -0,0 +1,66 @@
+/* Copyright (C) 2017 The Android Open Source Project
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This file implements interfaces from the file jvmti.h. This implementation
+ * is licensed under the same terms as the file jvmti.h.  The
+ * copyright and license information for the file jvmti.h follows.
+ *
+ * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef ART_RUNTIME_OPENJDKJVMTI_TI_PHASE_H_
+#define ART_RUNTIME_OPENJDKJVMTI_TI_PHASE_H_
+
+#include "jni.h"
+#include "jvmti.h"
+
+namespace openjdkjvmti {
+
+class EventHandler;
+
+class PhaseUtil {
+ public:
+  static jvmtiError GetPhase(jvmtiEnv* env, jvmtiPhase* phase_ptr);
+
+  static void Register(EventHandler* event_handler);
+  static void Unregister();
+
+  // Move the phase from unitialized to LOAD.
+  static void SetToOnLoad();
+
+  // Move the phase from LOAD to PRIMORDIAL.
+  static void SetToPrimordial();
+
+  // Move the phase from unitialized to LIVE.
+  static void SetToLive();
+
+  struct PhaseCallback;
+
+ private:
+  static jvmtiPhase current_phase_;
+};
+
+}  // namespace openjdkjvmti
+
+#endif  // ART_RUNTIME_OPENJDKJVMTI_TI_PHASE_H_
diff --git a/runtime/openjdkjvmti/ti_redefine.cc b/runtime/openjdkjvmti/ti_redefine.cc
index 6af51c4..34efc50 100644
--- a/runtime/openjdkjvmti/ti_redefine.cc
+++ b/runtime/openjdkjvmti/ti_redefine.cc
@@ -36,6 +36,7 @@
 #include "android-base/stringprintf.h"
 
 #include "art_jvmti.h"
+#include "base/array_slice.h"
 #include "base/logging.h"
 #include "dex_file.h"
 #include "dex_file_types.h"
@@ -228,11 +229,17 @@
   return map;
 }
 
-Redefiner::ClassRedefinition::ClassRedefinition(Redefiner* driver,
-                                                jclass klass,
-                                                const art::DexFile* redefined_dex_file,
-                                                const char* class_sig) :
-      driver_(driver), klass_(klass), dex_file_(redefined_dex_file), class_sig_(class_sig) {
+Redefiner::ClassRedefinition::ClassRedefinition(
+    Redefiner* driver,
+    jclass klass,
+    const art::DexFile* redefined_dex_file,
+    const char* class_sig,
+    art::ArraySlice<const unsigned char> orig_dex_file) :
+      driver_(driver),
+      klass_(klass),
+      dex_file_(redefined_dex_file),
+      class_sig_(class_sig),
+      original_dex_file_(orig_dex_file) {
   GetMirrorClass()->MonitorEnter(driver_->self_);
 }
 
@@ -242,14 +249,12 @@
   }
 }
 
-// TODO This should handle doing multiple classes at once so we need to do less cleanup when things
-// go wrong.
 jvmtiError Redefiner::RedefineClasses(ArtJvmTiEnv* env,
                                       art::Runtime* runtime,
                                       art::Thread* self,
                                       jint class_count,
                                       const jvmtiClassDefinition* definitions,
-                                      std::string* error_msg) {
+                                      /*out*/std::string* error_msg) {
   if (env == nullptr) {
     *error_msg = "env was null!";
     return ERR(INVALID_ENVIRONMENT);
@@ -263,46 +268,95 @@
     *error_msg = "null definitions!";
     return ERR(NULL_POINTER);
   }
+  std::vector<ArtClassDefinition> def_vector;
+  def_vector.reserve(class_count);
+  for (jint i = 0; i < class_count; i++) {
+    // We make a copy of the class_bytes to pass into the retransformation.
+    // This makes cleanup easier (since we unambiguously own the bytes) and also is useful since we
+    // will need to keep the original bytes around unaltered for subsequent RetransformClasses calls
+    // to get the passed in bytes.
+    // TODO Implement saving the original bytes.
+    unsigned char* class_bytes_copy = nullptr;
+    jvmtiError res = env->Allocate(definitions[i].class_byte_count, &class_bytes_copy);
+    if (res != OK) {
+      return res;
+    }
+    memcpy(class_bytes_copy, definitions[i].class_bytes, definitions[i].class_byte_count);
+
+    ArtClassDefinition def;
+    def.dex_len = definitions[i].class_byte_count;
+    def.dex_data = MakeJvmtiUniquePtr(env, class_bytes_copy);
+    // We are definitely modified.
+    def.SetModified();
+    def.original_dex_file = art::ArraySlice<const unsigned char>(definitions[i].class_bytes,
+                                                                 definitions[i].class_byte_count);
+    res = Transformer::FillInTransformationData(env, definitions[i].klass, &def);
+    if (res != OK) {
+      return res;
+    }
+    def_vector.push_back(std::move(def));
+  }
+  // Call all the transformation events.
+  jvmtiError res = Transformer::RetransformClassesDirect(env,
+                                                         self,
+                                                         &def_vector);
+  if (res != OK) {
+    // Something went wrong with transformation!
+    return res;
+  }
+  return RedefineClassesDirect(env, runtime, self, def_vector, error_msg);
+}
+
+jvmtiError Redefiner::RedefineClassesDirect(ArtJvmTiEnv* env,
+                                            art::Runtime* runtime,
+                                            art::Thread* self,
+                                            const std::vector<ArtClassDefinition>& definitions,
+                                            std::string* error_msg) {
+  DCHECK(env != nullptr);
+  if (definitions.size() == 0) {
+    // We don't actually need to do anything. Just return OK.
+    return OK;
+  }
   // Stop JIT for the duration of this redefine since the JIT might concurrently compile a method we
   // are going to redefine.
   art::jit::ScopedJitSuspend suspend_jit;
   // Get shared mutator lock so we can lock all the classes.
   art::ScopedObjectAccess soa(self);
-  std::vector<Redefiner::ClassRedefinition> redefinitions;
-  redefinitions.reserve(class_count);
   Redefiner r(runtime, self, error_msg);
-  for (jint i = 0; i < class_count; i++) {
-    jvmtiError res = r.AddRedefinition(env, definitions[i]);
-    if (res != OK) {
-      return res;
+  for (const ArtClassDefinition& def : definitions) {
+    // Only try to transform classes that have been modified.
+    if (def.IsModified(self)) {
+      jvmtiError res = r.AddRedefinition(env, def);
+      if (res != OK) {
+        return res;
+      }
     }
   }
   return r.Run();
 }
 
-jvmtiError Redefiner::AddRedefinition(ArtJvmTiEnv* env, const jvmtiClassDefinition& def) {
+jvmtiError Redefiner::AddRedefinition(ArtJvmTiEnv* env, const ArtClassDefinition& def) {
   std::string original_dex_location;
   jvmtiError ret = OK;
   if ((ret = GetClassLocation(env, def.klass, &original_dex_location))) {
     *error_msg_ = "Unable to get original dex file location!";
     return ret;
   }
-  std::unique_ptr<art::MemMap> map(MoveDataToMemMap(original_dex_location,
-                                                    def.class_byte_count,
-                                                    def.class_bytes,
-                                                    error_msg_));
-  std::ostringstream os;
   char* generic_ptr_unused = nullptr;
   char* signature_ptr = nullptr;
-  if (env->GetClassSignature(def.klass, &signature_ptr, &generic_ptr_unused) != OK) {
-    *error_msg_ = "A jclass passed in does not seem to be valid";
-    return ERR(INVALID_CLASS);
+  if ((ret = env->GetClassSignature(def.klass, &signature_ptr, &generic_ptr_unused)) != OK) {
+    *error_msg_ = "Unable to get class signature!";
+    return ret;
   }
-  // These will make sure we deallocate the signature.
-  JvmtiUniquePtr sig_unique_ptr(MakeJvmtiUniquePtr(env, signature_ptr));
   JvmtiUniquePtr generic_unique_ptr(MakeJvmtiUniquePtr(env, generic_ptr_unused));
+  JvmtiUniquePtr signature_unique_ptr(MakeJvmtiUniquePtr(env, signature_ptr));
+  std::unique_ptr<art::MemMap> map(MoveDataToMemMap(original_dex_location,
+                                                    def.dex_len,
+                                                    def.dex_data.get(),
+                                                    error_msg_));
+  std::ostringstream os;
   if (map.get() == nullptr) {
-    os << "Failed to create anonymous mmap for modified dex file of class " << signature_ptr
+    os << "Failed to create anonymous mmap for modified dex file of class " << def.name
        << "in dex file " << original_dex_location << " because: " << *error_msg_;
     *error_msg_ = os.str();
     return ERR(OUT_OF_MEMORY);
@@ -319,12 +373,16 @@
                                                                   /*verify_checksum*/true,
                                                                   error_msg_));
   if (dex_file.get() == nullptr) {
-    os << "Unable to load modified dex file for " << signature_ptr << ": " << *error_msg_;
+    os << "Unable to load modified dex file for " << def.name << ": " << *error_msg_;
     *error_msg_ = os.str();
     return ERR(INVALID_CLASS_FORMAT);
   }
   redefinitions_.push_back(
-      Redefiner::ClassRedefinition(this, def.klass, dex_file.release(), signature_ptr));
+      Redefiner::ClassRedefinition(this,
+                                   def.klass,
+                                   dex_file.release(),
+                                   signature_ptr,
+                                   def.original_dex_file));
   return OK;
 }
 
@@ -462,44 +520,48 @@
   result_ = result;
 }
 
-bool Redefiner::ClassRedefinition::FinishRemainingAllocations(
-    /*out*/art::MutableHandle<art::mirror::ClassLoader>* source_class_loader,
-    /*out*/art::MutableHandle<art::mirror::Object>* java_dex_file_obj,
-    /*out*/art::MutableHandle<art::mirror::LongArray>* new_dex_file_cookie,
-    /*out*/art::MutableHandle<art::mirror::DexCache>* new_dex_cache) {
-  art::StackHandleScope<4> hs(driver_->self_);
-  // This shouldn't allocate
-  art::Handle<art::mirror::ClassLoader> loader(hs.NewHandle(GetClassLoader()));
-  if (loader.Get() == nullptr) {
-    // TODO Better error msg.
-    RecordFailure(ERR(INTERNAL), "Unable to find class loader!");
-    return false;
+// Allocates a ByteArray big enough to store the given number of bytes and copies them from the
+// bytes pointer.
+static art::mirror::ByteArray* AllocateAndFillBytes(art::Thread* self,
+                                                    const uint8_t* bytes,
+                                                    int32_t num_bytes)
+    REQUIRES_SHARED(art::Locks::mutator_lock_) {
+  art::StackHandleScope<1> hs(self);
+  art::Handle<art::mirror::ByteArray> arr(hs.NewHandle(
+      art::mirror::ByteArray::Alloc(self, num_bytes)));
+  if (!arr.IsNull()) {
+    // Copy it in. Just skip if it's null
+    memcpy(arr->GetData(), bytes, num_bytes);
   }
-  art::Handle<art::mirror::Object> dex_file_obj(hs.NewHandle(FindSourceDexFileObject(loader)));
-  if (dex_file_obj.Get() == nullptr) {
-    // TODO Better error msg.
-    RecordFailure(ERR(INTERNAL), "Unable to find class loader!");
-    return false;
+  return arr.Get();
+}
+
+art::mirror::ByteArray* Redefiner::ClassRedefinition::AllocateOrGetOriginalDexFileBytes() {
+  // If we have been specifically given a new set of bytes use that
+  if (original_dex_file_.size() != 0) {
+    return AllocateAndFillBytes(driver_->self_,
+                                &original_dex_file_.At(0),
+                                original_dex_file_.size());
   }
-  art::Handle<art::mirror::LongArray> new_cookie(hs.NewHandle(AllocateDexFileCookie(dex_file_obj)));
-  if (new_cookie.Get() == nullptr) {
-    driver_->self_->AssertPendingOOMException();
-    driver_->self_->ClearException();
-    RecordFailure(ERR(OUT_OF_MEMORY), "Unable to allocate dex file array for class loader");
-    return false;
+
+  // See if we already have one set.
+  art::ObjPtr<art::mirror::ClassExt> ext(GetMirrorClass()->GetExtData());
+  if (!ext.IsNull()) {
+    art::ObjPtr<art::mirror::ByteArray> old_original_bytes(ext->GetOriginalDexFileBytes());
+    if (!old_original_bytes.IsNull()) {
+      // We do. Use it.
+      return old_original_bytes.Ptr();
+    }
   }
-  art::Handle<art::mirror::DexCache> dex_cache(hs.NewHandle(CreateNewDexCache(loader)));
-  if (dex_cache.Get() == nullptr) {
-    driver_->self_->AssertPendingOOMException();
-    driver_->self_->ClearException();
-    RecordFailure(ERR(OUT_OF_MEMORY), "Unable to allocate DexCache");
-    return false;
+
+  // Copy the current dex_file
+  const art::DexFile& current_dex_file = GetMirrorClass()->GetDexFile();
+  // TODO Handle this or make it so it cannot happen.
+  if (current_dex_file.NumClassDefs() != 1) {
+    LOG(WARNING) << "Current dex file has more than one class in it. Calling RetransformClasses "
+                 << "on this class might fail if no transformations are applied to it!";
   }
-  source_class_loader->Assign(loader.Get());
-  java_dex_file_obj->Assign(dex_file_obj.Get());
-  new_dex_file_cookie->Assign(new_cookie.Get());
-  new_dex_cache->Assign(dex_cache.Get());
-  return true;
+  return AllocateAndFillBytes(driver_->self_, current_dex_file.Begin(), current_dex_file.Size());
 }
 
 struct CallbackCtx {
@@ -694,9 +756,10 @@
     kSlotNewDexFileCookie = 2,
     kSlotNewDexCache = 3,
     kSlotMirrorClass = 4,
+    kSlotOrigDexFile = 5,
 
     // Must be last one.
-    kNumSlots = 5,
+    kNumSlots = 6,
   };
 
   // This needs to have a HandleScope passed in that is capable of creating a new Handle without
@@ -737,6 +800,11 @@
     return art::down_cast<art::mirror::Class*>(GetSlot(klass_index, kSlotMirrorClass));
   }
 
+  art::mirror::ByteArray* GetOriginalDexFileBytes(jint klass_index)
+      REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    return art::down_cast<art::mirror::ByteArray*>(GetSlot(klass_index, kSlotOrigDexFile));
+  }
+
   void SetSourceClassLoader(jint klass_index, art::mirror::ClassLoader* loader)
       REQUIRES_SHARED(art::Locks::mutator_lock_) {
     SetSlot(klass_index, kSlotSourceClassLoader, loader);
@@ -757,6 +825,10 @@
       REQUIRES_SHARED(art::Locks::mutator_lock_) {
     SetSlot(klass_index, kSlotMirrorClass, klass);
   }
+  void SetOriginalDexFileBytes(jint klass_index, art::mirror::ByteArray* bytes)
+      REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    SetSlot(klass_index, kSlotOrigDexFile, bytes);
+  }
 
   int32_t Length() REQUIRES_SHARED(art::Locks::mutator_lock_) {
     return arr_->GetLength() / kNumSlots;
@@ -782,6 +854,51 @@
   DISALLOW_COPY_AND_ASSIGN(RedefinitionDataHolder);
 };
 
+bool Redefiner::ClassRedefinition::FinishRemainingAllocations(
+    int32_t klass_index, /*out*/RedefinitionDataHolder* holder) {
+  art::StackHandleScope<2> hs(driver_->self_);
+  holder->SetMirrorClass(klass_index, GetMirrorClass());
+  // This shouldn't allocate
+  art::Handle<art::mirror::ClassLoader> loader(hs.NewHandle(GetClassLoader()));
+  holder->SetSourceClassLoader(klass_index, loader.Get());
+  if (loader.Get() == nullptr) {
+    // TODO Better error msg.
+    RecordFailure(ERR(INTERNAL), "Unable to find class loader!");
+    return false;
+  }
+  art::Handle<art::mirror::Object> dex_file_obj(hs.NewHandle(FindSourceDexFileObject(loader)));
+  holder->SetJavaDexFile(klass_index, dex_file_obj.Get());
+  if (dex_file_obj.Get() == nullptr) {
+    // TODO Better error msg.
+    RecordFailure(ERR(INTERNAL), "Unable to find class loader!");
+    return false;
+  }
+  holder->SetNewDexFileCookie(klass_index, AllocateDexFileCookie(dex_file_obj));
+  if (holder->GetNewDexFileCookie(klass_index) == nullptr) {
+    driver_->self_->AssertPendingOOMException();
+    driver_->self_->ClearException();
+    RecordFailure(ERR(OUT_OF_MEMORY), "Unable to allocate dex file array for class loader");
+    return false;
+  }
+  holder->SetNewDexCache(klass_index, CreateNewDexCache(loader));
+  if (holder->GetNewDexCache(klass_index) == nullptr) {
+    driver_->self_->AssertPendingOOMException();
+    driver_->self_->ClearException();
+    RecordFailure(ERR(OUT_OF_MEMORY), "Unable to allocate DexCache");
+    return false;
+  }
+
+  // We won't always need to set this field.
+  holder->SetOriginalDexFileBytes(klass_index, AllocateOrGetOriginalDexFileBytes());
+  if (holder->GetOriginalDexFileBytes(klass_index) == nullptr) {
+    driver_->self_->AssertPendingOOMException();
+    driver_->self_->ClearException();
+    RecordFailure(ERR(OUT_OF_MEMORY), "Unable to allocate array for original dex file");
+    return false;
+  }
+  return true;
+}
+
 bool Redefiner::CheckAllRedefinitionAreValid() {
   for (Redefiner::ClassRedefinition& redef : redefinitions_) {
     if (!redef.CheckRedefinitionIsValid()) {
@@ -802,33 +919,11 @@
 
 bool Redefiner::FinishAllRemainingAllocations(RedefinitionDataHolder& holder) {
   int32_t cnt = 0;
-  art::StackHandleScope<4> hs(self_);
-  art::MutableHandle<art::mirror::Object> java_dex_file(hs.NewHandle<art::mirror::Object>(nullptr));
-  art::MutableHandle<art::mirror::ClassLoader> source_class_loader(
-      hs.NewHandle<art::mirror::ClassLoader>(nullptr));
-  art::MutableHandle<art::mirror::LongArray> new_dex_file_cookie(
-      hs.NewHandle<art::mirror::LongArray>(nullptr));
-  art::MutableHandle<art::mirror::DexCache> new_dex_cache(
-      hs.NewHandle<art::mirror::DexCache>(nullptr));
   for (Redefiner::ClassRedefinition& redef : redefinitions_) {
-    // Reset the out pointers to null
-    source_class_loader.Assign(nullptr);
-    java_dex_file.Assign(nullptr);
-    new_dex_file_cookie.Assign(nullptr);
-    new_dex_cache.Assign(nullptr);
     // Allocate the data this redefinition requires.
-    if (!redef.FinishRemainingAllocations(&source_class_loader,
-                                          &java_dex_file,
-                                          &new_dex_file_cookie,
-                                          &new_dex_cache)) {
+    if (!redef.FinishRemainingAllocations(cnt, &holder)) {
       return false;
     }
-    // Save the allocated data into the holder.
-    holder.SetSourceClassLoader(cnt, source_class_loader.Get());
-    holder.SetJavaDexFile(cnt, java_dex_file.Get());
-    holder.SetNewDexFileCookie(cnt, new_dex_file_cookie.Get());
-    holder.SetNewDexCache(cnt, new_dex_cache.Get());
-    holder.SetMirrorClass(cnt, redef.GetMirrorClass());
     cnt++;
   }
   return true;
@@ -894,7 +989,7 @@
     redef.UpdateJavaDexFile(holder.GetJavaDexFile(cnt), holder.GetNewDexFileCookie(cnt));
     // TODO Rewrite so we don't do a stack walk for each and every class.
     redef.FindAndAllocateObsoleteMethods(klass);
-    redef.UpdateClass(klass, holder.GetNewDexCache(cnt));
+    redef.UpdateClass(klass, holder.GetNewDexCache(cnt), holder.GetOriginalDexFileBytes(cnt));
     cnt++;
   }
   // Ensure that obsolete methods are deoptimized. This is needed since optimized methods may have
@@ -987,20 +1082,24 @@
 }
 
 // Performs updates to class that will allow us to verify it.
-void Redefiner::ClassRedefinition::UpdateClass(art::ObjPtr<art::mirror::Class> mclass,
-                                               art::ObjPtr<art::mirror::DexCache> new_dex_cache) {
-  const art::DexFile::ClassDef* class_def = art::OatFile::OatDexFile::FindClassDef(
-      *dex_file_, class_sig_.c_str(), art::ComputeModifiedUtf8Hash(class_sig_.c_str()));
-  DCHECK(class_def != nullptr);
-  UpdateMethods(mclass, new_dex_cache, *class_def);
+void Redefiner::ClassRedefinition::UpdateClass(
+    art::ObjPtr<art::mirror::Class> mclass,
+    art::ObjPtr<art::mirror::DexCache> new_dex_cache,
+    art::ObjPtr<art::mirror::ByteArray> original_dex_file) {
+  DCHECK_EQ(dex_file_->NumClassDefs(), 1u);
+  const art::DexFile::ClassDef& class_def = dex_file_->GetClassDef(0);
+  UpdateMethods(mclass, new_dex_cache, class_def);
   UpdateFields(mclass);
 
   // Update the class fields.
   // Need to update class last since the ArtMethod gets its DexFile from the class (which is needed
   // to call GetReturnTypeDescriptor and GetParameterTypeList above).
   mclass->SetDexCache(new_dex_cache.Ptr());
-  mclass->SetDexClassDefIndex(dex_file_->GetIndexForClassDef(*class_def));
+  mclass->SetDexClassDefIndex(dex_file_->GetIndexForClassDef(class_def));
   mclass->SetDexTypeIndex(dex_file_->GetIndexForTypeId(*dex_file_->FindTypeId(class_sig_.c_str())));
+  art::ObjPtr<art::mirror::ClassExt> ext(mclass->GetExtData());
+  CHECK(!ext.IsNull());
+  ext->SetOriginalDexFileBytes(original_dex_file);
 }
 
 void Redefiner::ClassRedefinition::UpdateJavaDexFile(
diff --git a/runtime/openjdkjvmti/ti_redefine.h b/runtime/openjdkjvmti/ti_redefine.h
index 8626bc5..29a7e1f 100644
--- a/runtime/openjdkjvmti/ti_redefine.h
+++ b/runtime/openjdkjvmti/ti_redefine.h
@@ -38,6 +38,7 @@
 
 #include "art_jvmti.h"
 #include "art_method.h"
+#include "base/array_slice.h"
 #include "class_linker.h"
 #include "dex_file.h"
 #include "gc_root-inl.h"
@@ -56,6 +57,7 @@
 #include "obj_ptr.h"
 #include "scoped_thread_state_change-inl.h"
 #include "stack.h"
+#include "ti_class_definition.h"
 #include "thread_list.h"
 #include "transform.h"
 #include "utf.h"
@@ -72,13 +74,25 @@
  public:
   // Redefine the given classes with the given dex data. Note this function does not take ownership
   // of the dex_data pointers. It is not used after this call however and may be freed if desired.
+  // The caller is responsible for freeing it. The runtime makes its own copy of the data. This
+  // function does not call the transformation events.
+  // TODO Check modified flag of the definitions.
+  static jvmtiError RedefineClassesDirect(ArtJvmTiEnv* env,
+                                          art::Runtime* runtime,
+                                          art::Thread* self,
+                                          const std::vector<ArtClassDefinition>& definitions,
+                                          /*out*/std::string* error_msg);
+
+  // Redefine the given classes with the given dex data. Note this function does not take ownership
+  // of the dex_data pointers. It is not used after this call however and may be freed if desired.
   // The caller is responsible for freeing it. The runtime makes its own copy of the data.
+  // TODO This function should call the transformation events.
   static jvmtiError RedefineClasses(ArtJvmTiEnv* env,
                                     art::Runtime* runtime,
                                     art::Thread* self,
                                     jint class_count,
                                     const jvmtiClassDefinition* definitions,
-                                    std::string* error_msg);
+                                    /*out*/std::string* error_msg);
 
   static jvmtiError IsModifiableClass(jvmtiEnv* env, jclass klass, jboolean* is_redefinable);
 
@@ -88,7 +102,8 @@
     ClassRedefinition(Redefiner* driver,
                       jclass klass,
                       const art::DexFile* redefined_dex_file,
-                      const char* class_sig)
+                      const char* class_sig,
+                      art::ArraySlice<const unsigned char> orig_dex_file)
       REQUIRES_SHARED(art::Locks::mutator_lock_);
 
     // NO_THREAD_SAFETY_ANALYSIS so we can unlock the class in the destructor.
@@ -99,7 +114,8 @@
         : driver_(other.driver_),
           klass_(other.klass_),
           dex_file_(std::move(other.dex_file_)),
-          class_sig_(std::move(other.class_sig_)) {
+          class_sig_(std::move(other.class_sig_)),
+          original_dex_file_(other.original_dex_file_) {
       other.driver_ = nullptr;
     }
 
@@ -118,15 +134,15 @@
     art::mirror::LongArray* AllocateDexFileCookie(art::Handle<art::mirror::Object> j_dex_file_obj)
         REQUIRES_SHARED(art::Locks::mutator_lock_);
 
+    // This may return nullptr with a OOME pending if allocation fails.
+    art::mirror::ByteArray* AllocateOrGetOriginalDexFileBytes()
+        REQUIRES_SHARED(art::Locks::mutator_lock_);
+
     void RecordFailure(jvmtiError e, const std::string& err) {
       driver_->RecordFailure(e, class_sig_, err);
     }
 
-    bool FinishRemainingAllocations(
-          /*out*/art::MutableHandle<art::mirror::ClassLoader>* source_class_loader,
-          /*out*/art::MutableHandle<art::mirror::Object>* source_dex_file_obj,
-          /*out*/art::MutableHandle<art::mirror::LongArray>* new_dex_file_cookie,
-          /*out*/art::MutableHandle<art::mirror::DexCache>* new_dex_cache)
+    bool FinishRemainingAllocations(int32_t klass_index, /*out*/RedefinitionDataHolder* holder)
         REQUIRES_SHARED(art::Locks::mutator_lock_);
 
     void FindAndAllocateObsoleteMethods(art::mirror::Class* art_klass)
@@ -179,7 +195,8 @@
         REQUIRES(art::Locks::mutator_lock_);
 
     void UpdateClass(art::ObjPtr<art::mirror::Class> mclass,
-                     art::ObjPtr<art::mirror::DexCache> new_dex_cache)
+                     art::ObjPtr<art::mirror::DexCache> new_dex_cache,
+                     art::ObjPtr<art::mirror::ByteArray> original_dex_file)
         REQUIRES(art::Locks::mutator_lock_);
 
     void ReleaseDexFile() REQUIRES_SHARED(art::Locks::mutator_lock_);
@@ -189,6 +206,7 @@
     jclass klass_;
     std::unique_ptr<const art::DexFile> dex_file_;
     std::string class_sig_;
+    art::ArraySlice<const unsigned char> original_dex_file_;
   };
 
   jvmtiError result_;
@@ -209,7 +227,7 @@
         redefinitions_(),
         error_msg_(error_msg) { }
 
-  jvmtiError AddRedefinition(ArtJvmTiEnv* env, const jvmtiClassDefinition& def)
+  jvmtiError AddRedefinition(ArtJvmTiEnv* env, const ArtClassDefinition& def)
       REQUIRES_SHARED(art::Locks::mutator_lock_);
 
   static jvmtiError GetClassRedefinitionError(art::Handle<art::mirror::Class> klass,
diff --git a/runtime/openjdkjvmti/ti_thread.cc b/runtime/openjdkjvmti/ti_thread.cc
index 2bcdd8c..b18a5cd 100644
--- a/runtime/openjdkjvmti/ti_thread.cc
+++ b/runtime/openjdkjvmti/ti_thread.cc
@@ -31,10 +31,12 @@
 
 #include "ti_thread.h"
 
+#include "android-base/strings.h"
 #include "art_field.h"
 #include "art_jvmti.h"
 #include "base/logging.h"
 #include "base/mutex.h"
+#include "events-inl.h"
 #include "gc/system_weak.h"
 #include "gc_root-inl.h"
 #include "jni_internal.h"
@@ -43,6 +45,8 @@
 #include "mirror/string.h"
 #include "obj_ptr.h"
 #include "runtime.h"
+#include "runtime_callbacks.h"
+#include "ScopedLocalRef.h"
 #include "scoped_thread_state_change-inl.h"
 #include "thread-inl.h"
 #include "thread_list.h"
@@ -50,6 +54,80 @@
 
 namespace openjdkjvmti {
 
+struct ThreadCallback : public art::ThreadLifecycleCallback, public art::RuntimePhaseCallback {
+  jthread GetThreadObject(art::Thread* self) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    if (self->GetPeer() == nullptr) {
+      return nullptr;
+    }
+    return self->GetJniEnv()->AddLocalReference<jthread>(self->GetPeer());
+  }
+  template <ArtJvmtiEvent kEvent>
+  void Post(art::Thread* self) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    DCHECK_EQ(self, art::Thread::Current());
+    ScopedLocalRef<jthread> thread(self->GetJniEnv(), GetThreadObject(self));
+    art::ScopedThreadSuspension sts(self, art::ThreadState::kNative);
+    event_handler->DispatchEvent<kEvent>(self,
+                                         reinterpret_cast<JNIEnv*>(self->GetJniEnv()),
+                                         thread.get());
+  }
+
+  void ThreadStart(art::Thread* self) OVERRIDE REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    if (!started) {
+      // Runtime isn't started. We only expect at most the signal handler or JIT threads to be
+      // started here.
+      if (art::kIsDebugBuild) {
+        std::string name;
+        self->GetThreadName(name);
+        if (name != "Signal Catcher" && !android::base::StartsWith(name, "Jit thread pool")) {
+          LOG(FATAL) << "Unexpected thread before start: " << name;
+        }
+      }
+      return;
+    }
+    Post<ArtJvmtiEvent::kThreadStart>(self);
+  }
+
+  void ThreadDeath(art::Thread* self) OVERRIDE REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    Post<ArtJvmtiEvent::kThreadEnd>(self);
+  }
+
+  void NextRuntimePhase(RuntimePhase phase) OVERRIDE REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    if (phase == RuntimePhase::kInit) {
+      // We moved to VMInit. Report the main thread as started (it was attached early, and must
+      // not be reported until Init.
+      started = true;
+      Post<ArtJvmtiEvent::kThreadStart>(art::Thread::Current());
+    }
+  }
+
+  EventHandler* event_handler = nullptr;
+  bool started = false;
+};
+
+ThreadCallback gThreadCallback;
+
+void ThreadUtil::Register(EventHandler* handler) {
+  art::Runtime* runtime = art::Runtime::Current();
+
+  gThreadCallback.started = runtime->IsStarted();
+  gThreadCallback.event_handler = handler;
+
+  art::ScopedThreadStateChange stsc(art::Thread::Current(),
+                                    art::ThreadState::kWaitingForDebuggerToAttach);
+  art::ScopedSuspendAll ssa("Add thread callback");
+  runtime->GetRuntimeCallbacks()->AddThreadLifecycleCallback(&gThreadCallback);
+  runtime->GetRuntimeCallbacks()->AddRuntimePhaseCallback(&gThreadCallback);
+}
+
+void ThreadUtil::Unregister() {
+  art::ScopedThreadStateChange stsc(art::Thread::Current(),
+                                    art::ThreadState::kWaitingForDebuggerToAttach);
+  art::ScopedSuspendAll ssa("Remove thread callback");
+  art::Runtime* runtime = art::Runtime::Current();
+  runtime->GetRuntimeCallbacks()->RemoveThreadLifecycleCallback(&gThreadCallback);
+  runtime->GetRuntimeCallbacks()->RemoveRuntimePhaseCallback(&gThreadCallback);
+}
+
 jvmtiError ThreadUtil::GetCurrentThread(jvmtiEnv* env ATTRIBUTE_UNUSED, jthread* thread_ptr) {
   art::Thread* self = art::Thread::Current();
 
@@ -443,4 +521,80 @@
   return ERR(NONE);
 }
 
+struct AgentData {
+  const void* arg;
+  jvmtiStartFunction proc;
+  jthread thread;
+  JavaVM* java_vm;
+  jvmtiEnv* jvmti_env;
+  jint priority;
+};
+
+static void* AgentCallback(void* arg) {
+  std::unique_ptr<AgentData> data(reinterpret_cast<AgentData*>(arg));
+  CHECK(data->thread != nullptr);
+
+  // We already have a peer. So call our special Attach function.
+  art::Thread* self = art::Thread::Attach("JVMTI Agent thread", true, data->thread);
+  CHECK(self != nullptr);
+  // The name in Attach() is only for logging. Set the thread name. This is important so
+  // that the thread is no longer seen as starting up.
+  {
+    art::ScopedObjectAccess soa(self);
+    self->SetThreadName("JVMTI Agent thread");
+  }
+
+  // Release the peer.
+  JNIEnv* env = self->GetJniEnv();
+  env->DeleteGlobalRef(data->thread);
+  data->thread = nullptr;
+
+  // Run the agent code.
+  data->proc(data->jvmti_env, env, const_cast<void*>(data->arg));
+
+  // Detach the thread.
+  int detach_result = data->java_vm->DetachCurrentThread();
+  CHECK_EQ(detach_result, 0);
+
+  return nullptr;
+}
+
+jvmtiError ThreadUtil::RunAgentThread(jvmtiEnv* jvmti_env,
+                                      jthread thread,
+                                      jvmtiStartFunction proc,
+                                      const void* arg,
+                                      jint priority) {
+  if (priority < JVMTI_THREAD_MIN_PRIORITY || priority > JVMTI_THREAD_MAX_PRIORITY) {
+    return ERR(INVALID_PRIORITY);
+  }
+  JNIEnv* env = art::Thread::Current()->GetJniEnv();
+  if (thread == nullptr || !env->IsInstanceOf(thread, art::WellKnownClasses::java_lang_Thread)) {
+    return ERR(INVALID_THREAD);
+  }
+  if (proc == nullptr) {
+    return ERR(NULL_POINTER);
+  }
+
+  std::unique_ptr<AgentData> data(new AgentData);
+  data->arg = arg;
+  data->proc = proc;
+  // We need a global ref for Java objects, as local refs will be invalid.
+  data->thread = env->NewGlobalRef(thread);
+  data->java_vm = art::Runtime::Current()->GetJavaVM();
+  data->jvmti_env = jvmti_env;
+  data->priority = priority;
+
+  pthread_t pthread;
+  int pthread_create_result = pthread_create(&pthread,
+                                             nullptr,
+                                             &AgentCallback,
+                                             reinterpret_cast<void*>(data.get()));
+  if (pthread_create_result != 0) {
+    return ERR(INTERNAL);
+  }
+  data.release();
+
+  return ERR(NONE);
+}
+
 }  // namespace openjdkjvmti
diff --git a/runtime/openjdkjvmti/ti_thread.h b/runtime/openjdkjvmti/ti_thread.h
index 290e9d4..f6f93ee 100644
--- a/runtime/openjdkjvmti/ti_thread.h
+++ b/runtime/openjdkjvmti/ti_thread.h
@@ -37,8 +37,13 @@
 
 namespace openjdkjvmti {
 
+class EventHandler;
+
 class ThreadUtil {
  public:
+  static void Register(EventHandler* event_handler);
+  static void Unregister();
+
   static jvmtiError GetAllThreads(jvmtiEnv* env, jint* threads_count_ptr, jthread** threads_ptr);
 
   static jvmtiError GetCurrentThread(jvmtiEnv* env, jthread* thread_ptr);
@@ -49,6 +54,12 @@
 
   static jvmtiError SetThreadLocalStorage(jvmtiEnv* env, jthread thread, const void* data);
   static jvmtiError GetThreadLocalStorage(jvmtiEnv* env, jthread thread, void** data_ptr);
+
+  static jvmtiError RunAgentThread(jvmtiEnv* env,
+                                   jthread thread,
+                                   jvmtiStartFunction proc,
+                                   const void* arg,
+                                   jint priority);
 };
 
 }  // namespace openjdkjvmti
diff --git a/runtime/openjdkjvmti/transform.cc b/runtime/openjdkjvmti/transform.cc
index f545125..745c0f5 100644
--- a/runtime/openjdkjvmti/transform.cc
+++ b/runtime/openjdkjvmti/transform.cc
@@ -38,6 +38,7 @@
 #include "class_linker.h"
 #include "dex_file.h"
 #include "dex_file_types.h"
+#include "events-inl.h"
 #include "gc_root-inl.h"
 #include "globals.h"
 #include "jni_env_ext-inl.h"
@@ -46,18 +47,81 @@
 #include "mem_map.h"
 #include "mirror/array.h"
 #include "mirror/class-inl.h"
+#include "mirror/class_ext.h"
 #include "mirror/class_loader-inl.h"
 #include "mirror/string-inl.h"
 #include "oat_file.h"
 #include "scoped_thread_state_change-inl.h"
 #include "stack.h"
 #include "thread_list.h"
+#include "ti_redefine.h"
 #include "transform.h"
 #include "utf.h"
 #include "utils/dex_cache_arrays_layout-inl.h"
 
 namespace openjdkjvmti {
 
+jvmtiError Transformer::RetransformClassesDirect(
+      ArtJvmTiEnv* env,
+      art::Thread* self,
+      /*in-out*/std::vector<ArtClassDefinition>* definitions) {
+  for (ArtClassDefinition& def : *definitions) {
+    jint new_len = -1;
+    unsigned char* new_data = nullptr;
+    gEventHandler.DispatchEvent<ArtJvmtiEvent::kClassFileLoadHookRetransformable>(
+        self,
+        GetJniEnv(env),
+        def.klass,
+        def.loader,
+        def.name.c_str(),
+        def.protection_domain,
+        def.dex_len,
+        static_cast<const unsigned char*>(def.dex_data.get()),
+        &new_len,
+        &new_data);
+    def.SetNewDexData(env, new_len, new_data);
+  }
+  return OK;
+}
+
+jvmtiError Transformer::RetransformClasses(ArtJvmTiEnv* env,
+                                           art::Runtime* runtime,
+                                           art::Thread* self,
+                                           jint class_count,
+                                           const jclass* classes,
+                                           /*out*/std::string* error_msg) {
+  if (env == nullptr) {
+    *error_msg = "env was null!";
+    return ERR(INVALID_ENVIRONMENT);
+  } else if (class_count < 0) {
+    *error_msg = "class_count was less then 0";
+    return ERR(ILLEGAL_ARGUMENT);
+  } else if (class_count == 0) {
+    // We don't actually need to do anything. Just return OK.
+    return OK;
+  } else if (classes == nullptr) {
+    *error_msg = "null classes!";
+    return ERR(NULL_POINTER);
+  }
+  // A holder that will Deallocate all the class bytes buffers on destruction.
+  std::vector<ArtClassDefinition> definitions;
+  jvmtiError res = OK;
+  for (jint i = 0; i < class_count; i++) {
+    ArtClassDefinition def;
+    res = FillInTransformationData(env, classes[i], &def);
+    if (res != OK) {
+      return res;
+    }
+    definitions.push_back(std::move(def));
+  }
+  res = RetransformClassesDirect(env, self, &definitions);
+  if (res != OK) {
+    return res;
+  }
+  return Redefiner::RedefineClassesDirect(env, runtime, self, definitions, error_msg);
+}
+
+// TODO Move this somewhere else, ti_class?
 jvmtiError GetClassLocation(ArtJvmTiEnv* env, jclass klass, /*out*/std::string* location) {
   JNIEnv* jni_env = nullptr;
   jint ret = env->art_vm->GetEnv(reinterpret_cast<void**>(&jni_env), JNI_VERSION_1_1);
@@ -73,42 +137,74 @@
   return OK;
 }
 
+static jvmtiError CopyDataIntoJvmtiBuffer(ArtJvmTiEnv* env,
+                                          const unsigned char* source,
+                                          jint len,
+                                          /*out*/unsigned char** dest) {
+  jvmtiError res = env->Allocate(len, dest);
+  if (res != OK) {
+    return res;
+  }
+  memcpy(reinterpret_cast<void*>(*dest),
+         reinterpret_cast<const void*>(source),
+         len);
+  return OK;
+}
+
+jvmtiError Transformer::GetDexDataForRetransformation(ArtJvmTiEnv* env,
+                                                      art::Handle<art::mirror::Class> klass,
+                                                      /*out*/jint* dex_data_len,
+                                                      /*out*/unsigned char** dex_data) {
+  art::StackHandleScope<2> hs(art::Thread::Current());
+  art::Handle<art::mirror::ClassExt> ext(hs.NewHandle(klass->GetExtData()));
+  if (!ext.IsNull()) {
+    art::Handle<art::mirror::ByteArray> orig_dex(hs.NewHandle(ext->GetOriginalDexFileBytes()));
+    if (!orig_dex.IsNull()) {
+      *dex_data_len = static_cast<jint>(orig_dex->GetLength());
+      return CopyDataIntoJvmtiBuffer(env,
+                                     reinterpret_cast<const unsigned char*>(orig_dex->GetData()),
+                                     *dex_data_len,
+                                     /*out*/dex_data);
+    }
+  }
+  // TODO De-quicken the dex file before passing it to the agents.
+  LOG(WARNING) << "Dex file is not de-quickened yet! Quickened dex instructions might be present";
+  const art::DexFile& dex = klass->GetDexFile();
+  *dex_data_len = static_cast<jint>(dex.Size());
+  return CopyDataIntoJvmtiBuffer(env, dex.Begin(), *dex_data_len, /*out*/dex_data);
+}
+
 // TODO Move this function somewhere more appropriate.
 // Gets the data surrounding the given class.
-jvmtiError GetTransformationData(ArtJvmTiEnv* env,
-                                 jclass klass,
-                                 /*out*/std::string* location,
-                                 /*out*/JNIEnv** jni_env_ptr,
-                                 /*out*/jobject* loader,
-                                 /*out*/std::string* name,
-                                 /*out*/jobject* protection_domain,
-                                 /*out*/jint* data_len,
-                                 /*out*/unsigned char** dex_data) {
-  jint ret = env->art_vm->GetEnv(reinterpret_cast<void**>(jni_env_ptr), JNI_VERSION_1_1);
-  if (ret != JNI_OK) {
+// TODO Make this less magical.
+jvmtiError Transformer::FillInTransformationData(ArtJvmTiEnv* env,
+                                                 jclass klass,
+                                                 ArtClassDefinition* def) {
+  JNIEnv* jni_env = GetJniEnv(env);
+  if (jni_env == nullptr) {
     // TODO Different error might be better?
     return ERR(INTERNAL);
   }
-  JNIEnv* jni_env = *jni_env_ptr;
   art::ScopedObjectAccess soa(jni_env);
   art::StackHandleScope<3> hs(art::Thread::Current());
   art::Handle<art::mirror::Class> hs_klass(hs.NewHandle(soa.Decode<art::mirror::Class>(klass)));
-  *loader = soa.AddLocalReference<jobject>(hs_klass->GetClassLoader());
-  *name = art::mirror::Class::ComputeName(hs_klass)->ToModifiedUtf8();
-  // TODO is this always null?
-  *protection_domain = nullptr;
-  const art::DexFile& dex = hs_klass->GetDexFile();
-  *location = dex.GetLocation();
-  *data_len = static_cast<jint>(dex.Size());
-  // TODO We should maybe change env->Allocate to allow us to mprotect this memory and stop writes.
-  jvmtiError alloc_error = env->Allocate(*data_len, dex_data);
-  if (alloc_error != OK) {
-    return alloc_error;
+  if (hs_klass.IsNull()) {
+    return ERR(INVALID_CLASS);
   }
-  // Copy the data into a temporary buffer.
-  memcpy(reinterpret_cast<void*>(*dex_data),
-          reinterpret_cast<const void*>(dex.Begin()),
-          *data_len);
+  def->klass = klass;
+  def->loader = soa.AddLocalReference<jobject>(hs_klass->GetClassLoader());
+  def->name = art::mirror::Class::ComputeName(hs_klass)->ToModifiedUtf8();
+  // TODO is this always null?
+  def->protection_domain = nullptr;
+  if (def->dex_data.get() == nullptr) {
+    unsigned char* new_data;
+    jvmtiError res = GetDexDataForRetransformation(env, hs_klass, &def->dex_len, &new_data);
+    if (res == OK) {
+      def->dex_data = MakeJvmtiUniquePtr(env, new_data);
+    } else {
+      return res;
+    }
+  }
   return OK;
 }
 
diff --git a/runtime/openjdkjvmti/transform.h b/runtime/openjdkjvmti/transform.h
index 0ad5099..65f2ae1 100644
--- a/runtime/openjdkjvmti/transform.h
+++ b/runtime/openjdkjvmti/transform.h
@@ -37,22 +37,37 @@
 #include <jni.h>
 
 #include "art_jvmti.h"
+#include "ti_class_definition.h"
 #include "jvmti.h"
 
 namespace openjdkjvmti {
 
 jvmtiError GetClassLocation(ArtJvmTiEnv* env, jclass klass, /*out*/std::string* location);
 
-// Gets the data surrounding the given class.
-jvmtiError GetTransformationData(ArtJvmTiEnv* env,
-                                 jclass klass,
-                                 /*out*/std::string* location,
-                                 /*out*/JNIEnv** jni_env_ptr,
-                                 /*out*/jobject* loader,
-                                 /*out*/std::string* name,
-                                 /*out*/jobject* protection_domain,
-                                 /*out*/jint* data_len,
-                                 /*out*/unsigned char** dex_data);
+class Transformer {
+ public:
+  static jvmtiError RetransformClassesDirect(
+      ArtJvmTiEnv* env, art::Thread* self, /*in-out*/std::vector<ArtClassDefinition>* definitions);
+
+  static jvmtiError RetransformClasses(ArtJvmTiEnv* env,
+                                       art::Runtime* runtime,
+                                       art::Thread* self,
+                                       jint class_count,
+                                       const jclass* classes,
+                                       /*out*/std::string* error_msg);
+
+  // Gets the data surrounding the given class.
+  static jvmtiError FillInTransformationData(ArtJvmTiEnv* env,
+                                             jclass klass,
+                                             ArtClassDefinition* def);
+
+ private:
+  static jvmtiError GetDexDataForRetransformation(ArtJvmTiEnv* env,
+                                                  art::Handle<art::mirror::Class> klass,
+                                                  /*out*/jint* dex_data_length,
+                                                  /*out*/unsigned char** dex_data)
+      REQUIRES_SHARED(art::Locks::mutator_lock_);
+};
 
 }  // namespace openjdkjvmti
 
diff --git a/runtime/parsed_options.cc b/runtime/parsed_options.cc
index a72159b..d1ad77c 100644
--- a/runtime/parsed_options.cc
+++ b/runtime/parsed_options.cc
@@ -302,6 +302,9 @@
           .IntoKey(M::Plugins)
       .Define("-Xfully-deoptable")
           .IntoKey(M::FullyDeoptable)
+      .Define("-XX:ThreadSuspendTimeout=_")  // in ms
+          .WithType<MillisecondsToNanoseconds>()  // store as ns
+          .IntoKey(M::ThreadSuspendTimeout)
       .Ignore({
           "-ea", "-da", "-enableassertions", "-disableassertions", "--runtime-arg", "-esa",
           "-dsa", "-enablesystemassertions", "-disablesystemassertions", "-Xrs", "-Xint:_",
@@ -724,6 +727,7 @@
   UsageMessage(stream, "  -XX:MaxSpinsBeforeThinLockInflation=integervalue\n");
   UsageMessage(stream, "  -XX:LongPauseLogThreshold=integervalue\n");
   UsageMessage(stream, "  -XX:LongGCLogThreshold=integervalue\n");
+  UsageMessage(stream, "  -XX:ThreadSuspendTimeout=integervalue\n");
   UsageMessage(stream, "  -XX:DumpGCPerformanceOnShutdown\n");
   UsageMessage(stream, "  -XX:DumpJITInfoOnShutdown\n");
   UsageMessage(stream, "  -XX:IgnoreMaxFootprint\n");
diff --git a/runtime/reflection.cc b/runtime/reflection.cc
index 75176f9..a2b4cb3 100644
--- a/runtime/reflection.cc
+++ b/runtime/reflection.cc
@@ -216,43 +216,54 @@
   }
 
   bool BuildArgArrayFromObjectArray(ObjPtr<mirror::Object> receiver,
-                                    ObjPtr<mirror::ObjectArray<mirror::Object>> args,
-                                    ArtMethod* m)
+                                    ObjPtr<mirror::ObjectArray<mirror::Object>> raw_args,
+                                    ArtMethod* m,
+                                    Thread* self)
       REQUIRES_SHARED(Locks::mutator_lock_) {
     const DexFile::TypeList* classes = m->GetParameterTypeList();
     // Set receiver if non-null (method is not static)
     if (receiver != nullptr) {
       Append(receiver);
     }
+    StackHandleScope<2> hs(self);
+    MutableHandle<mirror::Object> arg(hs.NewHandle<mirror::Object>(nullptr));
+    Handle<mirror::ObjectArray<mirror::Object>> args(
+        hs.NewHandle<mirror::ObjectArray<mirror::Object>>(raw_args));
     for (size_t i = 1, args_offset = 0; i < shorty_len_; ++i, ++args_offset) {
-      ObjPtr<mirror::Object> arg(args->Get(args_offset));
-      if (((shorty_[i] == 'L') && (arg != nullptr)) || ((arg == nullptr && shorty_[i] != 'L'))) {
-        // Note: The method's parameter's type must have been previously resolved.
+      arg.Assign(args->Get(args_offset));
+      if (((shorty_[i] == 'L') && (arg.Get() != nullptr)) ||
+          ((arg.Get() == nullptr && shorty_[i] != 'L'))) {
+        // TODO: The method's parameter's type must have been previously resolved, yet
+        // we've seen cases where it's not b/34440020.
         ObjPtr<mirror::Class> dst_class(
             m->GetClassFromTypeIndex(classes->GetTypeItem(args_offset).type_idx_,
-                                     false /* resolve */));
-        DCHECK(dst_class != nullptr) << m->PrettyMethod() << " arg #" << i;
-        if (UNLIKELY(arg == nullptr || !arg->InstanceOf(dst_class))) {
+                                     true /* resolve */));
+        if (dst_class.Ptr() == nullptr) {
+          CHECK(self->IsExceptionPending());
+          return false;
+        }
+        if (UNLIKELY(arg.Get() == nullptr || !arg->InstanceOf(dst_class))) {
           ThrowIllegalArgumentException(
               StringPrintf("method %s argument %zd has type %s, got %s",
                   m->PrettyMethod(false).c_str(),
                   args_offset + 1,  // Humans don't count from 0.
                   mirror::Class::PrettyDescriptor(dst_class).c_str(),
-                  mirror::Object::PrettyTypeOf(arg).c_str()).c_str());
+                  mirror::Object::PrettyTypeOf(arg.Get()).c_str()).c_str());
           return false;
         }
       }
 
 #define DO_FIRST_ARG(match_descriptor, get_fn, append) { \
-          if (LIKELY(arg != nullptr && arg->GetClass()->DescriptorEquals(match_descriptor))) { \
+          if (LIKELY(arg.Get() != nullptr && \
+              arg->GetClass()->DescriptorEquals(match_descriptor))) { \
             ArtField* primitive_field = arg->GetClass()->GetInstanceField(0); \
-            append(primitive_field-> get_fn(arg));
+            append(primitive_field-> get_fn(arg.Get()));
 
 #define DO_ARG(match_descriptor, get_fn, append) \
-          } else if (LIKELY(arg != nullptr && \
+          } else if (LIKELY(arg.Get() != nullptr && \
                             arg->GetClass<>()->DescriptorEquals(match_descriptor))) { \
             ArtField* primitive_field = arg->GetClass()->GetInstanceField(0); \
-            append(primitive_field-> get_fn(arg));
+            append(primitive_field-> get_fn(arg.Get()));
 
 #define DO_FAIL(expected) \
           } else { \
@@ -266,14 +277,14 @@
                       ArtMethod::PrettyMethod(m, false).c_str(), \
                       args_offset + 1, \
                       expected, \
-                      mirror::Object::PrettyTypeOf(arg).c_str()).c_str()); \
+                      mirror::Object::PrettyTypeOf(arg.Get()).c_str()).c_str()); \
             } \
             return false; \
           } }
 
       switch (shorty_[i]) {
         case 'L':
-          Append(arg);
+          Append(arg.Get());
           break;
         case 'Z':
           DO_FIRST_ARG("Ljava/lang/Boolean;", GetBoolean, Append)
@@ -646,7 +657,7 @@
   uint32_t shorty_len = 0;
   const char* shorty = np_method->GetShorty(&shorty_len);
   ArgArray arg_array(shorty, shorty_len);
-  if (!arg_array.BuildArgArrayFromObjectArray(receiver, objects, np_method)) {
+  if (!arg_array.BuildArgArrayFromObjectArray(receiver, objects, np_method, soa.Self())) {
     CHECK(soa.Self()->IsExceptionPending());
     return nullptr;
   }
diff --git a/runtime/runtime.cc b/runtime/runtime.cc
index 55e1852..06cd7ff 100644
--- a/runtime/runtime.cc
+++ b/runtime/runtime.cc
@@ -137,6 +137,7 @@
 #include "jit/profile_saver.h"
 #include "quick/quick_method_frame_info.h"
 #include "reflection.h"
+#include "runtime_callbacks.h"
 #include "runtime_options.h"
 #include "ScopedLocalRef.h"
 #include "scoped_thread_state_change-inl.h"
@@ -253,10 +254,12 @@
       pruned_dalvik_cache_(false),
       // Initially assume we perceive jank in case the process state is never updated.
       process_state_(kProcessStateJankPerceptible),
-      zygote_no_threads_(false) {
+      zygote_no_threads_(false),
+      cha_(nullptr) {
   CheckAsmSupportOffsetsAndSizes();
   std::fill(callee_save_methods_, callee_save_methods_ + arraysize(callee_save_methods_), 0u);
   interpreter::CheckInterpreterAsmConstants();
+  callbacks_.reset(new RuntimeCallbacks());
 }
 
 Runtime::~Runtime() {
@@ -301,6 +304,13 @@
 
   Trace::Shutdown();
 
+  // Report death. Clients me require a working thread, still, so do it before GC completes and
+  // all non-daemon threads are done.
+  {
+    ScopedObjectAccess soa(self);
+    callbacks_->NextRuntimePhase(RuntimePhaseCallback::RuntimePhase::kDeath);
+  }
+
   if (attach_shutdown_thread) {
     DetachCurrentThread();
     self = nullptr;
@@ -703,6 +713,13 @@
 
   Thread::FinishStartup();
 
+  // Send the start phase event. We have to wait till here as this is when the main thread peer
+  // has just been generated, important root clinits have been run and JNI is completely functional.
+  {
+    ScopedObjectAccess soa(self);
+    callbacks_->NextRuntimePhase(RuntimePhaseCallback::RuntimePhase::kStart);
+  }
+
   system_class_loader_ = CreateSystemClassLoader(this);
 
   if (!is_zygote_) {
@@ -718,6 +735,13 @@
                             GetInstructionSetString(kRuntimeISA));
   }
 
+  // Send the initialized phase event. Send it before starting daemons, as otherwise
+  // sending thread events becomes complicated.
+  {
+    ScopedObjectAccess soa(self);
+    callbacks_->NextRuntimePhase(RuntimePhaseCallback::RuntimePhase::kInit);
+  }
+
   StartDaemonThreads();
 
   {
@@ -1022,7 +1046,7 @@
 
   monitor_list_ = new MonitorList;
   monitor_pool_ = MonitorPool::Create();
-  thread_list_ = new ThreadList;
+  thread_list_ = new ThreadList(runtime_options.GetOrDefault(Opt::ThreadSuspendTimeout));
   intern_table_ = new InternTable;
 
   verify_ = runtime_options.GetOrDefault(Opt::Verify);
@@ -1100,6 +1124,8 @@
   if (runtime_options.Exists(Opt::JdwpOptions)) {
     Dbg::ConfigureJdwp(runtime_options.GetOrDefault(Opt::JdwpOptions));
   }
+  callbacks_->AddThreadLifecycleCallback(Dbg::GetThreadLifecycleCallback());
+  callbacks_->AddClassLoadCallback(Dbg::GetClassLoadCallback());
 
   jit_options_.reset(jit::JitOptions::CreateFromRuntimeArguments(runtime_options));
   if (IsAotCompiler()) {
@@ -1358,6 +1384,10 @@
       LOG(ERROR) << "Unable to load an agent: " << err;
     }
   }
+  {
+    ScopedObjectAccess soa(self);
+    callbacks_->NextRuntimePhase(RuntimePhaseCallback::RuntimePhase::kInitialAgents);
+  }
 
   VLOG(startup) << "Runtime::Init exiting";
 
@@ -1370,7 +1400,7 @@
   constexpr const char* plugin_name = kIsDebugBuild ? "libopenjdkjvmtid.so" : "libopenjdkjvmti.so";
 
   // Is the plugin already loaded?
-  for (Plugin p : *plugins) {
+  for (const Plugin& p : *plugins) {
     if (p.GetLibrary() == plugin_name) {
       return true;
     }
@@ -1547,6 +1577,12 @@
 
   thread_list_->DumpForSigQuit(os);
   BaseMutex::DumpAll(os);
+
+  // Inform anyone else who is interested in SigQuit.
+  {
+    ScopedObjectAccess soa(Thread::Current());
+    callbacks_->SigQuit();
+  }
 }
 
 void Runtime::DumpLockHolders(std::ostream& os) {
@@ -2253,4 +2289,8 @@
   Runtime::Abort(abort_message);
 }
 
+RuntimeCallbacks* Runtime::GetRuntimeCallbacks() {
+  return callbacks_.get();
+}
+
 }  // namespace art
diff --git a/runtime/runtime.h b/runtime/runtime.h
index cf23d05..f7d6810 100644
--- a/runtime/runtime.h
+++ b/runtime/runtime.h
@@ -28,6 +28,7 @@
 
 #include "arch/instruction_set.h"
 #include "base/macros.h"
+#include "base/mutex.h"
 #include "dex_file_types.h"
 #include "experimental_flags.h"
 #include "gc_root.h"
@@ -89,6 +90,7 @@
 class OatFileManager;
 class Plugin;
 struct RuntimeArgumentMap;
+class RuntimeCallbacks;
 class SignalCatcher;
 class StackOverflowHandler;
 class SuspensionHandler;
@@ -659,6 +661,8 @@
 
   void AttachAgent(const std::string& agent_arg);
 
+  RuntimeCallbacks* GetRuntimeCallbacks();
+
  private:
   static void InitPlatformSignalHandlers();
 
@@ -916,6 +920,8 @@
 
   ClassHierarchyAnalysis* cha_;
 
+  std::unique_ptr<RuntimeCallbacks> callbacks_;
+
   DISALLOW_COPY_AND_ASSIGN(Runtime);
 };
 std::ostream& operator<<(std::ostream& os, const Runtime::CalleeSaveType& rhs);
diff --git a/runtime/runtime_callbacks.cc b/runtime/runtime_callbacks.cc
new file mode 100644
index 0000000..25324b5
--- /dev/null
+++ b/runtime/runtime_callbacks.cc
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "runtime_callbacks.h"
+
+#include <algorithm>
+
+#include "base/macros.h"
+#include "class_linker.h"
+#include "thread.h"
+
+namespace art {
+
+void RuntimeCallbacks::AddThreadLifecycleCallback(ThreadLifecycleCallback* cb) {
+  thread_callbacks_.push_back(cb);
+}
+
+template <typename T>
+ALWAYS_INLINE
+static inline void Remove(T* cb, std::vector<T*>* data) {
+  auto it = std::find(data->begin(), data->end(), cb);
+  if (it != data->end()) {
+    data->erase(it);
+  }
+}
+
+void RuntimeCallbacks::RemoveThreadLifecycleCallback(ThreadLifecycleCallback* cb) {
+  Remove(cb, &thread_callbacks_);
+}
+
+void RuntimeCallbacks::ThreadStart(Thread* self) {
+  for (ThreadLifecycleCallback* cb : thread_callbacks_) {
+    cb->ThreadStart(self);
+  }
+}
+
+void RuntimeCallbacks::ThreadDeath(Thread* self) {
+  for (ThreadLifecycleCallback* cb : thread_callbacks_) {
+    cb->ThreadDeath(self);
+  }
+}
+
+void RuntimeCallbacks::AddClassLoadCallback(ClassLoadCallback* cb) {
+  class_callbacks_.push_back(cb);
+}
+
+void RuntimeCallbacks::RemoveClassLoadCallback(ClassLoadCallback* cb) {
+  Remove(cb, &class_callbacks_);
+}
+
+void RuntimeCallbacks::ClassLoad(Handle<mirror::Class> klass) {
+  for (ClassLoadCallback* cb : class_callbacks_) {
+    cb->ClassLoad(klass);
+  }
+}
+
+void RuntimeCallbacks::ClassPreDefine(const char* descriptor,
+                                      Handle<mirror::Class> temp_class,
+                                      Handle<mirror::ClassLoader> loader,
+                                      const DexFile& initial_dex_file,
+                                      const DexFile::ClassDef& initial_class_def,
+                                      /*out*/DexFile const** final_dex_file,
+                                      /*out*/DexFile::ClassDef const** final_class_def) {
+  DexFile const* current_dex_file = &initial_dex_file;
+  DexFile::ClassDef const* current_class_def = &initial_class_def;
+  for (ClassLoadCallback* cb : class_callbacks_) {
+    DexFile const* new_dex_file = nullptr;
+    DexFile::ClassDef const* new_class_def = nullptr;
+    cb->ClassPreDefine(descriptor,
+                       temp_class,
+                       loader,
+                       *current_dex_file,
+                       *current_class_def,
+                       &new_dex_file,
+                       &new_class_def);
+    if ((new_dex_file != nullptr && new_dex_file != current_dex_file) ||
+        (new_class_def != nullptr && new_class_def != current_class_def)) {
+      DCHECK(new_dex_file != nullptr && new_class_def != nullptr);
+      current_dex_file = new_dex_file;
+      current_class_def = new_class_def;
+    }
+  }
+  *final_dex_file = current_dex_file;
+  *final_class_def = current_class_def;
+}
+
+void RuntimeCallbacks::ClassPrepare(Handle<mirror::Class> temp_klass, Handle<mirror::Class> klass) {
+  for (ClassLoadCallback* cb : class_callbacks_) {
+    cb->ClassPrepare(temp_klass, klass);
+  }
+}
+
+void RuntimeCallbacks::AddRuntimeSigQuitCallback(RuntimeSigQuitCallback* cb) {
+  sigquit_callbacks_.push_back(cb);
+}
+
+void RuntimeCallbacks::RemoveRuntimeSigQuitCallback(RuntimeSigQuitCallback* cb) {
+  Remove(cb, &sigquit_callbacks_);
+}
+
+void RuntimeCallbacks::SigQuit() {
+  for (RuntimeSigQuitCallback* cb : sigquit_callbacks_) {
+    cb->SigQuit();
+  }
+}
+
+void RuntimeCallbacks::AddRuntimePhaseCallback(RuntimePhaseCallback* cb) {
+  phase_callbacks_.push_back(cb);
+}
+
+void RuntimeCallbacks::RemoveRuntimePhaseCallback(RuntimePhaseCallback* cb) {
+  Remove(cb, &phase_callbacks_);
+}
+
+void RuntimeCallbacks::NextRuntimePhase(RuntimePhaseCallback::RuntimePhase phase) {
+  for (RuntimePhaseCallback* cb : phase_callbacks_) {
+    cb->NextRuntimePhase(phase);
+  }
+}
+
+}  // namespace art
diff --git a/runtime/runtime_callbacks.h b/runtime/runtime_callbacks.h
new file mode 100644
index 0000000..d321254
--- /dev/null
+++ b/runtime/runtime_callbacks.h
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ART_RUNTIME_RUNTIME_CALLBACKS_H_
+#define ART_RUNTIME_RUNTIME_CALLBACKS_H_
+
+#include <vector>
+
+#include "base/macros.h"
+#include "base/mutex.h"
+#include "dex_file.h"
+#include "handle.h"
+
+namespace art {
+
+namespace mirror {
+class Class;
+class ClassLoader;
+}  // namespace mirror
+
+class ClassLoadCallback;
+class Thread;
+class ThreadLifecycleCallback;
+
+// Note: RuntimeCallbacks uses the mutator lock to synchronize the callback lists. A thread must
+//       hold the exclusive lock to add or remove a listener. A thread must hold the shared lock
+//       to dispatch an event. This setup is chosen as some clients may want to suspend the
+//       dispatching thread or all threads.
+//
+//       To make this safe, the following restrictions apply:
+//       * Only the owner of a listener may ever add or remove said listener.
+//       * A listener must never add or remove itself or any other listener while running.
+//       * It is the responsibility of the owner to not remove the listener while it is running
+//         (and suspended).
+//
+//       The simplest way to satisfy these restrictions is to never remove a listener, and to do
+//       any state checking (is the listener enabled) in the listener itself. For an example, see
+//       Dbg.
+
+class RuntimeSigQuitCallback {
+ public:
+  virtual ~RuntimeSigQuitCallback() {}
+
+  virtual void SigQuit() REQUIRES_SHARED(Locks::mutator_lock_) = 0;
+};
+
+class RuntimePhaseCallback {
+ public:
+  enum RuntimePhase {
+    kInitialAgents,   // Initial agent loading is done.
+    kStart,           // The runtime is started.
+    kInit,            // The runtime is initialized (and will run user code soon).
+    kDeath,           // The runtime just died.
+  };
+
+  virtual ~RuntimePhaseCallback() {}
+
+  virtual void NextRuntimePhase(RuntimePhase phase) REQUIRES_SHARED(Locks::mutator_lock_) = 0;
+};
+
+class RuntimeCallbacks {
+ public:
+  void AddThreadLifecycleCallback(ThreadLifecycleCallback* cb) REQUIRES(Locks::mutator_lock_);
+  void RemoveThreadLifecycleCallback(ThreadLifecycleCallback* cb) REQUIRES(Locks::mutator_lock_);
+
+  void ThreadStart(Thread* self) REQUIRES_SHARED(Locks::mutator_lock_);
+  void ThreadDeath(Thread* self) REQUIRES_SHARED(Locks::mutator_lock_);
+
+  void AddClassLoadCallback(ClassLoadCallback* cb) REQUIRES(Locks::mutator_lock_);
+  void RemoveClassLoadCallback(ClassLoadCallback* cb) REQUIRES(Locks::mutator_lock_);
+
+  void ClassLoad(Handle<mirror::Class> klass) REQUIRES_SHARED(Locks::mutator_lock_);
+  void ClassPrepare(Handle<mirror::Class> temp_klass, Handle<mirror::Class> klass)
+      REQUIRES_SHARED(Locks::mutator_lock_);
+
+  void AddRuntimeSigQuitCallback(RuntimeSigQuitCallback* cb)
+      REQUIRES(Locks::mutator_lock_);
+  void RemoveRuntimeSigQuitCallback(RuntimeSigQuitCallback* cb)
+      REQUIRES(Locks::mutator_lock_);
+
+  void SigQuit() REQUIRES_SHARED(Locks::mutator_lock_);
+
+  void AddRuntimePhaseCallback(RuntimePhaseCallback* cb)
+      REQUIRES(Locks::mutator_lock_);
+  void RemoveRuntimePhaseCallback(RuntimePhaseCallback* cb)
+      REQUIRES(Locks::mutator_lock_);
+
+  void NextRuntimePhase(RuntimePhaseCallback::RuntimePhase phase)
+      REQUIRES_SHARED(Locks::mutator_lock_);
+
+  void ClassPreDefine(const char* descriptor,
+                      Handle<mirror::Class> temp_class,
+                      Handle<mirror::ClassLoader> loader,
+                      const DexFile& initial_dex_file,
+                      const DexFile::ClassDef& initial_class_def,
+                      /*out*/DexFile const** final_dex_file,
+                      /*out*/DexFile::ClassDef const** final_class_def)
+      REQUIRES_SHARED(Locks::mutator_lock_);
+
+ private:
+  std::vector<ThreadLifecycleCallback*> thread_callbacks_
+      GUARDED_BY(Locks::mutator_lock_);
+  std::vector<ClassLoadCallback*> class_callbacks_
+      GUARDED_BY(Locks::mutator_lock_);
+  std::vector<RuntimeSigQuitCallback*> sigquit_callbacks_
+      GUARDED_BY(Locks::mutator_lock_);
+  std::vector<RuntimePhaseCallback*> phase_callbacks_
+        GUARDED_BY(Locks::mutator_lock_);
+};
+
+}  // namespace art
+
+#endif  // ART_RUNTIME_RUNTIME_CALLBACKS_H_
diff --git a/runtime/runtime_callbacks_test.cc b/runtime/runtime_callbacks_test.cc
new file mode 100644
index 0000000..f1e78b4
--- /dev/null
+++ b/runtime/runtime_callbacks_test.cc
@@ -0,0 +1,432 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "runtime_callbacks.h"
+
+#include "jni.h"
+#include <signal.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <initializer_list>
+#include <memory>
+#include <string>
+
+#include "art_method-inl.h"
+#include "base/mutex.h"
+#include "class_linker.h"
+#include "common_runtime_test.h"
+#include "handle.h"
+#include "handle_scope-inl.h"
+#include "mem_map.h"
+#include "mirror/class-inl.h"
+#include "mirror/class_loader.h"
+#include "obj_ptr.h"
+#include "runtime.h"
+#include "scoped_thread_state_change-inl.h"
+#include "ScopedLocalRef.h"
+#include "thread-inl.h"
+#include "thread_list.h"
+#include "well_known_classes.h"
+
+namespace art {
+
+class RuntimeCallbacksTest : public CommonRuntimeTest {
+ protected:
+  void SetUp() OVERRIDE {
+    CommonRuntimeTest::SetUp();
+
+    Thread* self = Thread::Current();
+    ScopedObjectAccess soa(self);
+    ScopedThreadSuspension sts(self, kWaitingForDebuggerToAttach);
+    ScopedSuspendAll ssa("RuntimeCallbacksTest SetUp");
+    AddListener();
+  }
+
+  void TearDown() OVERRIDE {
+    {
+      Thread* self = Thread::Current();
+      ScopedObjectAccess soa(self);
+      ScopedThreadSuspension sts(self, kWaitingForDebuggerToAttach);
+      ScopedSuspendAll ssa("RuntimeCallbacksTest TearDown");
+      RemoveListener();
+    }
+
+    CommonRuntimeTest::TearDown();
+  }
+
+  virtual void AddListener() REQUIRES(Locks::mutator_lock_) = 0;
+  virtual void RemoveListener() REQUIRES(Locks::mutator_lock_) = 0;
+
+  void MakeExecutable(ObjPtr<mirror::Class> klass) REQUIRES_SHARED(Locks::mutator_lock_) {
+    CHECK(klass != nullptr);
+    PointerSize pointer_size = class_linker_->GetImagePointerSize();
+    for (auto& m : klass->GetMethods(pointer_size)) {
+      if (!m.IsAbstract()) {
+        class_linker_->SetEntryPointsToInterpreter(&m);
+      }
+    }
+  }
+};
+
+class ThreadLifecycleCallbackRuntimeCallbacksTest : public RuntimeCallbacksTest {
+ public:
+  static void* PthreadsCallback(void* arg ATTRIBUTE_UNUSED) {
+    // Attach.
+    Runtime* runtime = Runtime::Current();
+    CHECK(runtime->AttachCurrentThread("ThreadLifecycle test thread", true, nullptr, false));
+
+    // Detach.
+    runtime->DetachCurrentThread();
+
+    // Die...
+    return nullptr;
+  }
+
+ protected:
+  void AddListener() OVERRIDE REQUIRES(Locks::mutator_lock_) {
+    Runtime::Current()->GetRuntimeCallbacks()->AddThreadLifecycleCallback(&cb_);
+  }
+  void RemoveListener() OVERRIDE REQUIRES(Locks::mutator_lock_) {
+    Runtime::Current()->GetRuntimeCallbacks()->RemoveThreadLifecycleCallback(&cb_);
+  }
+
+  enum CallbackState {
+    kBase,
+    kStarted,
+    kDied,
+    kWrongStart,
+    kWrongDeath,
+  };
+
+  struct Callback : public ThreadLifecycleCallback {
+    void ThreadStart(Thread* self) OVERRIDE {
+      if (state == CallbackState::kBase) {
+        state = CallbackState::kStarted;
+        stored_self = self;
+      } else {
+        state = CallbackState::kWrongStart;
+      }
+    }
+
+    void ThreadDeath(Thread* self) OVERRIDE {
+      if (state == CallbackState::kStarted && self == stored_self) {
+        state = CallbackState::kDied;
+      } else {
+        state = CallbackState::kWrongDeath;
+      }
+    }
+
+    Thread* stored_self;
+    CallbackState state = CallbackState::kBase;
+  };
+
+  Callback cb_;
+};
+
+TEST_F(ThreadLifecycleCallbackRuntimeCallbacksTest, ThreadLifecycleCallbackJava) {
+  Thread* self = Thread::Current();
+
+  self->TransitionFromSuspendedToRunnable();
+  bool started = runtime_->Start();
+  ASSERT_TRUE(started);
+
+  cb_.state = CallbackState::kBase;  // Ignore main thread attach.
+
+  {
+    ScopedObjectAccess soa(self);
+    MakeExecutable(soa.Decode<mirror::Class>(WellKnownClasses::java_lang_Thread));
+  }
+
+  JNIEnv* env = self->GetJniEnv();
+
+  ScopedLocalRef<jobject> thread_name(env,
+                                      env->NewStringUTF("ThreadLifecycleCallback test thread"));
+  ASSERT_TRUE(thread_name.get() != nullptr);
+
+  ScopedLocalRef<jobject> thread(env, env->AllocObject(WellKnownClasses::java_lang_Thread));
+  ASSERT_TRUE(thread.get() != nullptr);
+
+  env->CallNonvirtualVoidMethod(thread.get(),
+                                WellKnownClasses::java_lang_Thread,
+                                WellKnownClasses::java_lang_Thread_init,
+                                runtime_->GetMainThreadGroup(),
+                                thread_name.get(),
+                                kMinThreadPriority,
+                                JNI_FALSE);
+  ASSERT_FALSE(env->ExceptionCheck());
+
+  jmethodID start_id = env->GetMethodID(WellKnownClasses::java_lang_Thread, "start", "()V");
+  ASSERT_TRUE(start_id != nullptr);
+
+  env->CallVoidMethod(thread.get(), start_id);
+  ASSERT_FALSE(env->ExceptionCheck());
+
+  jmethodID join_id = env->GetMethodID(WellKnownClasses::java_lang_Thread, "join", "()V");
+  ASSERT_TRUE(join_id != nullptr);
+
+  env->CallVoidMethod(thread.get(), join_id);
+  ASSERT_FALSE(env->ExceptionCheck());
+
+  EXPECT_TRUE(cb_.state == CallbackState::kDied) << static_cast<int>(cb_.state);
+}
+
+TEST_F(ThreadLifecycleCallbackRuntimeCallbacksTest, ThreadLifecycleCallbackAttach) {
+  std::string error_msg;
+  std::unique_ptr<MemMap> stack(MemMap::MapAnonymous("ThreadLifecycleCallback Thread",
+                                                     nullptr,
+                                                     128 * kPageSize,  // Just some small stack.
+                                                     PROT_READ | PROT_WRITE,
+                                                     false,
+                                                     false,
+                                                     &error_msg));
+  ASSERT_FALSE(stack == nullptr) << error_msg;
+
+  const char* reason = "ThreadLifecycleCallback test thread";
+  pthread_attr_t attr;
+  CHECK_PTHREAD_CALL(pthread_attr_init, (&attr), reason);
+  CHECK_PTHREAD_CALL(pthread_attr_setstack, (&attr, stack->Begin(), stack->Size()), reason);
+  pthread_t pthread;
+  CHECK_PTHREAD_CALL(pthread_create,
+                     (&pthread,
+                         &attr,
+                         &ThreadLifecycleCallbackRuntimeCallbacksTest::PthreadsCallback,
+                         this),
+                         reason);
+  CHECK_PTHREAD_CALL(pthread_attr_destroy, (&attr), reason);
+
+  CHECK_PTHREAD_CALL(pthread_join, (pthread, nullptr), "ThreadLifecycleCallback test shutdown");
+
+  // Detach is not a ThreadDeath event, so we expect to be in state Started.
+  EXPECT_TRUE(cb_.state == CallbackState::kStarted) << static_cast<int>(cb_.state);
+}
+
+class ClassLoadCallbackRuntimeCallbacksTest : public RuntimeCallbacksTest {
+ protected:
+  void AddListener() OVERRIDE REQUIRES(Locks::mutator_lock_) {
+    Runtime::Current()->GetRuntimeCallbacks()->AddClassLoadCallback(&cb_);
+  }
+  void RemoveListener() OVERRIDE REQUIRES(Locks::mutator_lock_) {
+    Runtime::Current()->GetRuntimeCallbacks()->RemoveClassLoadCallback(&cb_);
+  }
+
+  bool Expect(std::initializer_list<const char*> list) {
+    if (cb_.data.size() != list.size()) {
+      PrintError(list);
+      return false;
+    }
+
+    if (!std::equal(cb_.data.begin(), cb_.data.end(), list.begin())) {
+      PrintError(list);
+      return false;
+    }
+
+    return true;
+  }
+
+  void PrintError(std::initializer_list<const char*> list) {
+    LOG(ERROR) << "Expected:";
+    for (const char* expected : list) {
+      LOG(ERROR) << "  " << expected;
+    }
+    LOG(ERROR) << "Found:";
+    for (const auto& s : cb_.data) {
+      LOG(ERROR) << "  " << s;
+    }
+  }
+
+  struct Callback : public ClassLoadCallback {
+    virtual void ClassPreDefine(const char* descriptor,
+                                Handle<mirror::Class> klass ATTRIBUTE_UNUSED,
+                                Handle<mirror::ClassLoader> class_loader ATTRIBUTE_UNUSED,
+                                const DexFile& initial_dex_file,
+                                const DexFile::ClassDef& initial_class_def ATTRIBUTE_UNUSED,
+                                /*out*/DexFile const** final_dex_file ATTRIBUTE_UNUSED,
+                                /*out*/DexFile::ClassDef const** final_class_def ATTRIBUTE_UNUSED)
+        OVERRIDE REQUIRES_SHARED(Locks::mutator_lock_) {
+      std::string location(initial_dex_file.GetLocation());
+      std::string event =
+          std::string("PreDefine:") + descriptor + " <" +
+          location.substr(location.rfind("/") + 1, location.size()) + ">";
+      data.push_back(event);
+    }
+
+    void ClassLoad(Handle<mirror::Class> klass) OVERRIDE REQUIRES_SHARED(Locks::mutator_lock_) {
+      std::string tmp;
+      std::string event = std::string("Load:") + klass->GetDescriptor(&tmp);
+      data.push_back(event);
+    }
+
+    void ClassPrepare(Handle<mirror::Class> temp_klass,
+                      Handle<mirror::Class> klass) OVERRIDE REQUIRES_SHARED(Locks::mutator_lock_) {
+      std::string tmp, tmp2;
+      std::string event = std::string("Prepare:") + klass->GetDescriptor(&tmp)
+          + "[" + temp_klass->GetDescriptor(&tmp2) + "]";
+      data.push_back(event);
+    }
+
+    std::vector<std::string> data;
+  };
+
+  Callback cb_;
+};
+
+TEST_F(ClassLoadCallbackRuntimeCallbacksTest, ClassLoadCallback) {
+  ScopedObjectAccess soa(Thread::Current());
+  jobject jclass_loader = LoadDex("XandY");
+  VariableSizedHandleScope hs(soa.Self());
+  Handle<mirror::ClassLoader> class_loader(hs.NewHandle(
+      soa.Decode<mirror::ClassLoader>(jclass_loader)));
+
+  const char* descriptor_y = "LY;";
+  Handle<mirror::Class> h_Y(
+      hs.NewHandle(class_linker_->FindClass(soa.Self(), descriptor_y, class_loader)));
+  ASSERT_TRUE(h_Y.Get() != nullptr);
+
+  bool expect1 = Expect({ "PreDefine:LY; <art-gtest-XandY.jar>",
+                          "PreDefine:LX; <art-gtest-XandY.jar>",
+                          "Load:LX;",
+                          "Prepare:LX;[LX;]",
+                          "Load:LY;",
+                          "Prepare:LY;[LY;]" });
+  EXPECT_TRUE(expect1);
+
+  cb_.data.clear();
+
+  ASSERT_TRUE(class_linker_->EnsureInitialized(Thread::Current(), h_Y, true, true));
+
+  bool expect2 = Expect({ "PreDefine:LY$Z; <art-gtest-XandY.jar>",
+                          "Load:LY$Z;",
+                          "Prepare:LY$Z;[LY$Z;]" });
+  EXPECT_TRUE(expect2);
+}
+
+class RuntimeSigQuitCallbackRuntimeCallbacksTest : public RuntimeCallbacksTest {
+ protected:
+  void AddListener() OVERRIDE REQUIRES(Locks::mutator_lock_) {
+    Runtime::Current()->GetRuntimeCallbacks()->AddRuntimeSigQuitCallback(&cb_);
+  }
+  void RemoveListener() OVERRIDE REQUIRES(Locks::mutator_lock_) {
+    Runtime::Current()->GetRuntimeCallbacks()->RemoveRuntimeSigQuitCallback(&cb_);
+  }
+
+  struct Callback : public RuntimeSigQuitCallback {
+    void SigQuit() OVERRIDE {
+      ++sigquit_count;
+    }
+
+    size_t sigquit_count = 0;
+  };
+
+  Callback cb_;
+};
+
+TEST_F(RuntimeSigQuitCallbackRuntimeCallbacksTest, SigQuit) {
+  // The runtime needs to be started for the signal handler.
+  Thread* self = Thread::Current();
+
+  self->TransitionFromSuspendedToRunnable();
+  bool started = runtime_->Start();
+  ASSERT_TRUE(started);
+
+  EXPECT_EQ(0u, cb_.sigquit_count);
+
+  kill(getpid(), SIGQUIT);
+
+  // Try a few times.
+  for (size_t i = 0; i != 30; ++i) {
+    if (cb_.sigquit_count == 0) {
+      sleep(1);
+    } else {
+      break;
+    }
+  }
+  EXPECT_EQ(1u, cb_.sigquit_count);
+}
+
+class RuntimePhaseCallbackRuntimeCallbacksTest : public RuntimeCallbacksTest {
+ protected:
+  void AddListener() OVERRIDE REQUIRES(Locks::mutator_lock_) {
+    Runtime::Current()->GetRuntimeCallbacks()->AddRuntimePhaseCallback(&cb_);
+  }
+  void RemoveListener() OVERRIDE REQUIRES(Locks::mutator_lock_) {
+    Runtime::Current()->GetRuntimeCallbacks()->RemoveRuntimePhaseCallback(&cb_);
+  }
+
+  void TearDown() OVERRIDE {
+    // Bypass RuntimeCallbacksTest::TearDown, as the runtime is already gone.
+    CommonRuntimeTest::TearDown();
+  }
+
+  struct Callback : public RuntimePhaseCallback {
+    void NextRuntimePhase(RuntimePhaseCallback::RuntimePhase p) OVERRIDE {
+      if (p == RuntimePhaseCallback::RuntimePhase::kInitialAgents) {
+        if (start_seen > 0 || init_seen > 0 || death_seen > 0) {
+          LOG(FATAL) << "Unexpected order";
+        }
+        ++initial_agents_seen;
+      } else if (p == RuntimePhaseCallback::RuntimePhase::kStart) {
+        if (init_seen > 0 || death_seen > 0) {
+          LOG(FATAL) << "Init seen before start.";
+        }
+        ++start_seen;
+      } else if (p == RuntimePhaseCallback::RuntimePhase::kInit) {
+        ++init_seen;
+      } else if (p == RuntimePhaseCallback::RuntimePhase::kDeath) {
+        ++death_seen;
+      } else {
+        LOG(FATAL) << "Unknown phase " << static_cast<uint32_t>(p);
+      }
+    }
+
+    size_t initial_agents_seen = 0;
+    size_t start_seen = 0;
+    size_t init_seen = 0;
+    size_t death_seen = 0;
+  };
+
+  Callback cb_;
+};
+
+TEST_F(RuntimePhaseCallbackRuntimeCallbacksTest, Phases) {
+  ASSERT_EQ(0u, cb_.initial_agents_seen);
+  ASSERT_EQ(0u, cb_.start_seen);
+  ASSERT_EQ(0u, cb_.init_seen);
+  ASSERT_EQ(0u, cb_.death_seen);
+
+  // Start the runtime.
+  {
+    Thread* self = Thread::Current();
+    self->TransitionFromSuspendedToRunnable();
+    bool started = runtime_->Start();
+    ASSERT_TRUE(started);
+  }
+
+  ASSERT_EQ(0u, cb_.initial_agents_seen);
+  ASSERT_EQ(1u, cb_.start_seen);
+  ASSERT_EQ(1u, cb_.init_seen);
+  ASSERT_EQ(0u, cb_.death_seen);
+
+  // Delete the runtime.
+  runtime_.reset();
+
+  ASSERT_EQ(0u, cb_.initial_agents_seen);
+  ASSERT_EQ(1u, cb_.start_seen);
+  ASSERT_EQ(1u, cb_.init_seen);
+  ASSERT_EQ(1u, cb_.death_seen);
+}
+
+}  // namespace art
diff --git a/runtime/runtime_options.cc b/runtime/runtime_options.cc
index e75481c..aa14719 100644
--- a/runtime/runtime_options.cc
+++ b/runtime/runtime_options.cc
@@ -21,6 +21,7 @@
 #include "gc/heap.h"
 #include "monitor.h"
 #include "runtime.h"
+#include "thread_list.h"
 #include "trace.h"
 #include "utils.h"
 #include "debugger.h"
diff --git a/runtime/runtime_options.def b/runtime/runtime_options.def
index ecabf9a..749a36e 100644
--- a/runtime/runtime_options.def
+++ b/runtime/runtime_options.def
@@ -60,6 +60,8 @@
                                           LongPauseLogThreshold,          gc::Heap::kDefaultLongPauseLogThreshold)
 RUNTIME_OPTIONS_KEY (MillisecondsToNanoseconds, \
                                           LongGCLogThreshold,             gc::Heap::kDefaultLongGCLogThreshold)
+RUNTIME_OPTIONS_KEY (MillisecondsToNanoseconds, \
+                                          ThreadSuspendTimeout,           ThreadList::kDefaultThreadSuspendTimeout)
 RUNTIME_OPTIONS_KEY (Unit,                DumpGCPerformanceOnShutdown)
 RUNTIME_OPTIONS_KEY (Unit,                DumpJITInfoOnShutdown)
 RUNTIME_OPTIONS_KEY (Unit,                IgnoreMaxFootprint)
diff --git a/runtime/stack_map.cc b/runtime/stack_map.cc
index 9ebf9a7..690b069 100644
--- a/runtime/stack_map.cc
+++ b/runtime/stack_map.cc
@@ -116,7 +116,8 @@
 void CodeInfo::Dump(VariableIndentationOutputStream* vios,
                     uint32_t code_offset,
                     uint16_t number_of_dex_registers,
-                    bool dump_stack_maps) const {
+                    bool dump_stack_maps,
+                    InstructionSet instruction_set) const {
   CodeInfoEncoding encoding = ExtractEncoding();
   size_t number_of_stack_maps = GetNumberOfStackMaps(encoding);
   vios->Stream()
@@ -139,6 +140,7 @@
                      encoding,
                      code_offset,
                      number_of_dex_registers,
+                     instruction_set,
                      " " + std::to_string(i));
     }
   }
@@ -188,14 +190,17 @@
                     const CodeInfoEncoding& encoding,
                     uint32_t code_offset,
                     uint16_t number_of_dex_registers,
+                    InstructionSet instruction_set,
                     const std::string& header_suffix) const {
   StackMapEncoding stack_map_encoding = encoding.stack_map_encoding;
+  const uint32_t pc_offset = GetNativePcOffset(stack_map_encoding, instruction_set);
   vios->Stream()
       << "StackMap" << header_suffix
       << std::hex
-      << " [native_pc=0x" << code_offset + GetNativePcOffset(stack_map_encoding) << "]"
+      << " [native_pc=0x" << code_offset + pc_offset << "]"
+      << " [entry_size=0x" << encoding.stack_map_size_in_bytes << "]"
       << " (dex_pc=0x" << GetDexPc(stack_map_encoding)
-      << ", native_pc_offset=0x" << GetNativePcOffset(stack_map_encoding)
+      << ", native_pc_offset=0x" << pc_offset
       << ", dex_register_map_offset=0x" << GetDexRegisterMapOffset(stack_map_encoding)
       << ", inline_info_offset=0x" << GetInlineDescriptorOffset(stack_map_encoding)
       << ", register_mask=0x" << GetRegisterMask(stack_map_encoding)
diff --git a/runtime/stack_map.h b/runtime/stack_map.h
index 13886f2..cd9a3f0 100644
--- a/runtime/stack_map.h
+++ b/runtime/stack_map.h
@@ -17,6 +17,7 @@
 #ifndef ART_RUNTIME_STACK_MAP_H_
 #define ART_RUNTIME_STACK_MAP_H_
 
+#include "arch/code_offset.h"
 #include "base/bit_vector.h"
 #include "base/bit_utils.h"
 #include "dex_file.h"
@@ -805,12 +806,16 @@
     encoding.GetDexPcEncoding().Store(region_, dex_pc);
   }
 
-  ALWAYS_INLINE uint32_t GetNativePcOffset(const StackMapEncoding& encoding) const {
-    return encoding.GetNativePcEncoding().Load(region_);
+  ALWAYS_INLINE uint32_t GetNativePcOffset(const StackMapEncoding& encoding,
+                                           InstructionSet instruction_set) const {
+    CodeOffset offset(
+        CodeOffset::FromCompressedOffset(encoding.GetNativePcEncoding().Load(region_)));
+    return offset.Uint32Value(instruction_set);
   }
 
-  ALWAYS_INLINE void SetNativePcOffset(const StackMapEncoding& encoding, uint32_t native_pc_offset) {
-    encoding.GetNativePcEncoding().Store(region_, native_pc_offset);
+  ALWAYS_INLINE void SetNativePcCodeOffset(const StackMapEncoding& encoding,
+                                           CodeOffset native_pc_offset) {
+    encoding.GetNativePcEncoding().Store(region_, native_pc_offset.CompressedValue());
   }
 
   ALWAYS_INLINE uint32_t GetDexRegisterMapOffset(const StackMapEncoding& encoding) const {
@@ -866,6 +871,7 @@
             const CodeInfoEncoding& encoding,
             uint32_t code_offset,
             uint16_t number_of_dex_registers,
+            InstructionSet instruction_set,
             const std::string& header_suffix = "") const;
 
   // Special (invalid) offset for the DexRegisterMapOffset field meaning
@@ -1176,6 +1182,17 @@
     }
   }
 
+  size_t GetDexRegisterMapsSize(const CodeInfoEncoding& encoding,
+                                uint32_t number_of_dex_registers) const {
+    size_t total = 0;
+    for (size_t i = 0, e = GetNumberOfStackMaps(encoding); i < e; ++i) {
+      StackMap stack_map = GetStackMapAt(i, encoding);
+      DexRegisterMap map(GetDexRegisterMapOf(stack_map, encoding, number_of_dex_registers));
+      total += map.Size();
+    }
+    return total;
+  }
+
   // Return the `DexRegisterMap` pointed by `inline_info` at depth `depth`.
   DexRegisterMap GetDexRegisterMapAtDepth(uint8_t depth,
                                           InlineInfo inline_info,
@@ -1234,15 +1251,16 @@
       if (stack_map.GetDexPc(stack_map_encoding) == dex_pc) {
         StackMap other = GetStackMapAt(i + 1, encoding);
         if (other.GetDexPc(stack_map_encoding) == dex_pc &&
-            other.GetNativePcOffset(stack_map_encoding) ==
-                stack_map.GetNativePcOffset(stack_map_encoding)) {
+            other.GetNativePcOffset(stack_map_encoding, kRuntimeISA) ==
+                stack_map.GetNativePcOffset(stack_map_encoding, kRuntimeISA)) {
           DCHECK_EQ(other.GetDexRegisterMapOffset(stack_map_encoding),
                     stack_map.GetDexRegisterMapOffset(stack_map_encoding));
           DCHECK(!stack_map.HasInlineInfo(stack_map_encoding));
           if (i < e - 2) {
             // Make sure there are not three identical stack maps following each other.
-            DCHECK_NE(stack_map.GetNativePcOffset(stack_map_encoding),
-                      GetStackMapAt(i + 2, encoding).GetNativePcOffset(stack_map_encoding));
+            DCHECK_NE(
+                stack_map.GetNativePcOffset(stack_map_encoding, kRuntimeISA),
+                GetStackMapAt(i + 2, encoding).GetNativePcOffset(stack_map_encoding, kRuntimeISA));
           }
           return stack_map;
         }
@@ -1258,7 +1276,8 @@
     //       we could do binary search.
     for (size_t i = 0, e = GetNumberOfStackMaps(encoding); i < e; ++i) {
       StackMap stack_map = GetStackMapAt(i, encoding);
-      if (stack_map.GetNativePcOffset(encoding.stack_map_encoding) == native_pc_offset) {
+      if (stack_map.GetNativePcOffset(encoding.stack_map_encoding, kRuntimeISA) ==
+          native_pc_offset) {
         return stack_map;
       }
     }
@@ -1273,7 +1292,8 @@
   void Dump(VariableIndentationOutputStream* vios,
             uint32_t code_offset,
             uint16_t number_of_dex_registers,
-            bool dump_stack_maps) const;
+            bool dump_stack_maps,
+            InstructionSet instruction_set) const;
 
   // Check that the code info has valid stack map and abort if it does not.
   void AssertValidStackMap(const CodeInfoEncoding& encoding) const {
diff --git a/runtime/thread.cc b/runtime/thread.cc
index ebf14c1..d93eab1 100644
--- a/runtime/thread.cc
+++ b/runtime/thread.cc
@@ -67,6 +67,7 @@
 #include "quick/quick_method_frame_info.h"
 #include "reflection.h"
 #include "runtime.h"
+#include "runtime_callbacks.h"
 #include "scoped_thread_state_change-inl.h"
 #include "ScopedLocalRef.h"
 #include "ScopedUtfChars.h"
@@ -431,7 +432,8 @@
 
     ArtField* priorityField = jni::DecodeArtField(WellKnownClasses::java_lang_Thread_priority);
     self->SetNativePriority(priorityField->GetInt(self->tlsPtr_.opeer));
-    Dbg::PostThreadStart(self);
+
+    runtime->GetRuntimeCallbacks()->ThreadStart(self);
 
     // Invoke the 'run' method of our java.lang.Thread.
     ObjPtr<mirror::Object> receiver = self->tlsPtr_.opeer;
@@ -723,8 +725,8 @@
   return true;
 }
 
-Thread* Thread::Attach(const char* thread_name, bool as_daemon, jobject thread_group,
-                       bool create_peer) {
+template <typename PeerAction>
+Thread* Thread::Attach(const char* thread_name, bool as_daemon, PeerAction peer_action) {
   Runtime* runtime = Runtime::Current();
   if (runtime == nullptr) {
     LOG(ERROR) << "Thread attaching to non-existent runtime: " << thread_name;
@@ -753,32 +755,11 @@
   CHECK_NE(self->GetState(), kRunnable);
   self->SetState(kNative);
 
-  // If we're the main thread, ClassLinker won't be created until after we're attached,
-  // so that thread needs a two-stage attach. Regular threads don't need this hack.
-  // In the compiler, all threads need this hack, because no-one's going to be getting
-  // a native peer!
-  if (create_peer) {
-    self->CreatePeer(thread_name, as_daemon, thread_group);
-    if (self->IsExceptionPending()) {
-      // We cannot keep the exception around, as we're deleting self. Try to be helpful and log it.
-      {
-        ScopedObjectAccess soa(self);
-        LOG(ERROR) << "Exception creating thread peer:";
-        LOG(ERROR) << self->GetException()->Dump();
-        self->ClearException();
-      }
-      runtime->GetThreadList()->Unregister(self);
-      // Unregister deletes self, no need to do this here.
-      return nullptr;
-    }
-  } else {
-    // These aren't necessary, but they improve diagnostics for unit tests & command-line tools.
-    if (thread_name != nullptr) {
-      self->tlsPtr_.name->assign(thread_name);
-      ::art::SetThreadName(thread_name);
-    } else if (self->GetJniEnv()->check_jni) {
-      LOG(WARNING) << *Thread::Current() << " attached without supplying a name";
-    }
+  // Run the action that is acting on the peer.
+  if (!peer_action(self)) {
+    runtime->GetThreadList()->Unregister(self);
+    // Unregister deletes self, no need to do this here.
+    return nullptr;
   }
 
   if (VLOG_IS_ON(threads)) {
@@ -793,12 +774,63 @@
 
   {
     ScopedObjectAccess soa(self);
-    Dbg::PostThreadStart(self);
+    runtime->GetRuntimeCallbacks()->ThreadStart(self);
   }
 
   return self;
 }
 
+Thread* Thread::Attach(const char* thread_name,
+                       bool as_daemon,
+                       jobject thread_group,
+                       bool create_peer) {
+  auto create_peer_action = [&](Thread* self) {
+    // If we're the main thread, ClassLinker won't be created until after we're attached,
+    // so that thread needs a two-stage attach. Regular threads don't need this hack.
+    // In the compiler, all threads need this hack, because no-one's going to be getting
+    // a native peer!
+    if (create_peer) {
+      self->CreatePeer(thread_name, as_daemon, thread_group);
+      if (self->IsExceptionPending()) {
+        // We cannot keep the exception around, as we're deleting self. Try to be helpful and log it.
+        {
+          ScopedObjectAccess soa(self);
+          LOG(ERROR) << "Exception creating thread peer:";
+          LOG(ERROR) << self->GetException()->Dump();
+          self->ClearException();
+        }
+        return false;
+      }
+    } else {
+      // These aren't necessary, but they improve diagnostics for unit tests & command-line tools.
+      if (thread_name != nullptr) {
+        self->tlsPtr_.name->assign(thread_name);
+        ::art::SetThreadName(thread_name);
+      } else if (self->GetJniEnv()->check_jni) {
+        LOG(WARNING) << *Thread::Current() << " attached without supplying a name";
+      }
+    }
+    return true;
+  };
+  return Attach(thread_name, as_daemon, create_peer_action);
+}
+
+Thread* Thread::Attach(const char* thread_name, bool as_daemon, jobject thread_peer) {
+  auto set_peer_action = [&](Thread* self) {
+    // Install the given peer.
+    {
+      DCHECK(self == Thread::Current());
+      ScopedObjectAccess soa(self);
+      self->tlsPtr_.opeer = soa.Decode<mirror::Object>(thread_peer).Ptr();
+    }
+    self->GetJniEnv()->SetLongField(thread_peer,
+                                    WellKnownClasses::java_lang_Thread_nativePeer,
+                                    reinterpret_cast<jlong>(self));
+    return true;
+  };
+  return Attach(thread_name, as_daemon, set_peer_action);
+}
+
 void Thread::CreatePeer(const char* name, bool as_daemon, jobject thread_group) {
   Runtime* runtime = Runtime::Current();
   CHECK(runtime->IsStarted());
@@ -1929,7 +1961,11 @@
       jni::DecodeArtField(WellKnownClasses::java_lang_Thread_nativePeer)
           ->SetLong<false>(tlsPtr_.opeer, 0);
     }
-    Dbg::PostThreadDeath(self);
+    Runtime* runtime = Runtime::Current();
+    if (runtime != nullptr) {
+      runtime->GetRuntimeCallbacks()->ThreadDeath(self);
+    }
+
 
     // Thread.join() is implemented as an Object.wait() on the Thread.lock object. Signal anyone
     // who is waiting.
diff --git a/runtime/thread.h b/runtime/thread.h
index 2b451bc..b609e72 100644
--- a/runtime/thread.h
+++ b/runtime/thread.h
@@ -158,6 +158,8 @@
   // Used to implement JNI AttachCurrentThread and AttachCurrentThreadAsDaemon calls.
   static Thread* Attach(const char* thread_name, bool as_daemon, jobject thread_group,
                         bool create_peer);
+  // Attaches the calling native thread to the runtime, returning the new native peer.
+  static Thread* Attach(const char* thread_name, bool as_daemon, jobject thread_peer);
 
   // Reset internal state of child thread after fork.
   void InitAfterFork();
@@ -1166,6 +1168,13 @@
   ~Thread() REQUIRES(!Locks::mutator_lock_, !Locks::thread_suspend_count_lock_);
   void Destroy();
 
+  // Attaches the calling native thread to the runtime, returning the new native peer.
+  // Used to implement JNI AttachCurrentThread and AttachCurrentThreadAsDaemon calls.
+  template <typename PeerAction>
+  static Thread* Attach(const char* thread_name,
+                        bool as_daemon,
+                        PeerAction p);
+
   void CreatePeer(const char* name, bool as_daemon, jobject thread_group);
 
   template<bool kTransactionActive>
@@ -1704,6 +1713,14 @@
   Thread* const self_;
 };
 
+class ThreadLifecycleCallback {
+ public:
+  virtual ~ThreadLifecycleCallback() {}
+
+  virtual void ThreadStart(Thread* self) REQUIRES_SHARED(Locks::mutator_lock_) = 0;
+  virtual void ThreadDeath(Thread* self) REQUIRES_SHARED(Locks::mutator_lock_) = 0;
+};
+
 std::ostream& operator<<(std::ostream& os, const Thread& thread);
 std::ostream& operator<<(std::ostream& os, const StackedShadowFrameType& thread);
 
diff --git a/runtime/thread_list.cc b/runtime/thread_list.cc
index c5c7e2c..01c940e 100644
--- a/runtime/thread_list.cc
+++ b/runtime/thread_list.cc
@@ -57,7 +57,6 @@
 using android::base::StringPrintf;
 
 static constexpr uint64_t kLongThreadSuspendThreshold = MsToNs(5);
-static constexpr uint64_t kThreadSuspendTimeoutMs = 30 * 1000;  // 30s.
 // Use 0 since we want to yield to prevent blocking for an unpredictable amount of time.
 static constexpr useconds_t kThreadSuspendInitialSleepUs = 0;
 static constexpr useconds_t kThreadSuspendMaxYieldUs = 3000;
@@ -68,12 +67,13 @@
 // Turned off again. b/29248079
 static constexpr bool kDumpUnattachedThreadNativeStackForSigQuit = false;
 
-ThreadList::ThreadList()
+ThreadList::ThreadList(uint64_t thread_suspend_timeout_ns)
     : suspend_all_count_(0),
       debug_suspend_all_count_(0),
       unregistering_count_(0),
       suspend_all_historam_("suspend all histogram", 16, 64),
       long_suspend_(false),
+      thread_suspend_timeout_ns_(thread_suspend_timeout_ns),
       empty_checkpoint_barrier_(new Barrier(0)) {
   CHECK(Monitor::IsValidLockWord(LockWord::FromThinLockId(kMaxThreadId, 1, 0U)));
 }
@@ -554,12 +554,14 @@
     // Make sure this thread grabs exclusive access to the mutator lock and its protected data.
 #if HAVE_TIMED_RWLOCK
     while (true) {
-      if (Locks::mutator_lock_->ExclusiveLockWithTimeout(self, kThreadSuspendTimeoutMs, 0)) {
+      if (Locks::mutator_lock_->ExclusiveLockWithTimeout(self,
+                                                         NsToMs(thread_suspend_timeout_ns_),
+                                                         0)) {
         break;
       } else if (!long_suspend_) {
         // Reading long_suspend without the mutator lock is slightly racy, in some rare cases, this
         // could result in a thread suspend timeout.
-        // Timeout if we wait more than kThreadSuspendTimeoutMs seconds.
+        // Timeout if we wait more than thread_suspend_timeout_ns_ nanoseconds.
         UnsafeLogFatalForThreadSuspendAllTimeout();
       }
     }
@@ -653,7 +655,7 @@
   // is done with a timeout so that we can detect problems.
 #if ART_USE_FUTEXES
   timespec wait_timeout;
-  InitTimeSpec(false, CLOCK_MONOTONIC, kIsDebugBuild ? 50000 : 10000, 0, &wait_timeout);
+  InitTimeSpec(false, CLOCK_MONOTONIC, NsToMs(thread_suspend_timeout_ns_), 0, &wait_timeout);
 #endif
   const uint64_t start_time = NanoTime();
   while (true) {
@@ -863,7 +865,7 @@
           return thread;
         }
         const uint64_t total_delay = NanoTime() - start_time;
-        if (total_delay >= MsToNs(kThreadSuspendTimeoutMs)) {
+        if (total_delay >= thread_suspend_timeout_ns_) {
           ThreadSuspendByPeerWarning(self,
                                      ::android::base::FATAL,
                                      "Thread suspension timed out",
@@ -969,7 +971,7 @@
           return thread;
         }
         const uint64_t total_delay = NanoTime() - start_time;
-        if (total_delay >= MsToNs(kThreadSuspendTimeoutMs)) {
+        if (total_delay >= thread_suspend_timeout_ns_) {
           ThreadSuspendByThreadIdWarning(::android::base::WARNING,
                                          "Thread suspension timed out",
                                          thread_id);
diff --git a/runtime/thread_list.h b/runtime/thread_list.h
index 658db00..b60fca1 100644
--- a/runtime/thread_list.h
+++ b/runtime/thread_list.h
@@ -20,6 +20,7 @@
 #include "barrier.h"
 #include "base/histogram.h"
 #include "base/mutex.h"
+#include "base/time_utils.h"
 #include "base/value_object.h"
 #include "gc_root.h"
 #include "jni.h"
@@ -41,11 +42,12 @@
 
 class ThreadList {
  public:
-  static const uint32_t kMaxThreadId = 0xFFFF;
-  static const uint32_t kInvalidThreadId = 0;
-  static const uint32_t kMainThreadId = 1;
+  static constexpr uint32_t kMaxThreadId = 0xFFFF;
+  static constexpr uint32_t kInvalidThreadId = 0;
+  static constexpr uint32_t kMainThreadId = 1;
+  static constexpr uint64_t kDefaultThreadSuspendTimeout = MsToNs(kIsDebugBuild ? 50000 : 10000);
 
-  explicit ThreadList();
+  explicit ThreadList(uint64_t thread_suspend_timeout_ns);
   ~ThreadList();
 
   void DumpForSigQuit(std::ostream& os)
@@ -219,6 +221,9 @@
   // Whether or not the current thread suspension is long.
   bool long_suspend_;
 
+  // Thread suspension timeout in nanoseconds.
+  const uint64_t thread_suspend_timeout_ns_;
+
   std::unique_ptr<Barrier> empty_checkpoint_barrier_;
 
   friend class Thread;
diff --git a/runtime/trace.cc b/runtime/trace.cc
index 9d9360e..2add955 100644
--- a/runtime/trace.cc
+++ b/runtime/trace.cc
@@ -54,6 +54,7 @@
     static_cast<size_t>(kTraceMethodActionMask));
 static constexpr uint8_t kOpNewMethod = 1U;
 static constexpr uint8_t kOpNewThread = 2U;
+static constexpr uint8_t kOpTraceSummary = 3U;
 
 class BuildStackTraceVisitor : public StackVisitor {
  public:
@@ -700,20 +701,19 @@
   std::string header(os.str());
 
   if (trace_output_mode_ == TraceOutputMode::kStreaming) {
-    File file(streaming_file_name_ + ".sec", O_CREAT | O_WRONLY, true);
-    if (!file.IsOpened()) {
-      LOG(WARNING) << "Could not open secondary trace file!";
-      return;
-    }
-    if (!file.WriteFully(header.c_str(), header.length())) {
-      file.Erase();
-      std::string detail(StringPrintf("Trace data write failed: %s", strerror(errno)));
-      PLOG(ERROR) << detail;
-      ThrowRuntimeException("%s", detail.c_str());
-    }
-    if (file.FlushCloseOrErase() != 0) {
-      PLOG(ERROR) << "Could not write secondary file";
-    }
+    MutexLock mu(Thread::Current(), *streaming_lock_);  // To serialize writing.
+    // Write a special token to mark the end of trace records and the start of
+    // trace summary.
+    uint8_t buf[7];
+    Append2LE(buf, 0);
+    buf[2] = kOpTraceSummary;
+    Append4LE(buf + 3, static_cast<uint32_t>(header.length()));
+    WriteToBuf(buf, sizeof(buf));
+    // Write the trace summary. The summary is identical to the file header when
+    // the output mode is not streaming (except for methods).
+    WriteToBuf(reinterpret_cast<const uint8_t*>(header.c_str()), header.length());
+    // Flush the buffer, which may include some trace records before the summary.
+    FlushBuf();
   } else {
     if (trace_file_.get() == nullptr) {
       iovec iov[2];
@@ -894,6 +894,14 @@
   memcpy(buf_.get() + old_offset, src, src_size);
 }
 
+void Trace::FlushBuf() {
+  int32_t offset = cur_offset_.LoadRelaxed();
+  if (!trace_file_->WriteFully(buf_.get(), offset)) {
+    PLOG(WARNING) << "Failed flush the remaining data in streaming.";
+  }
+  cur_offset_.StoreRelease(0);
+}
+
 void Trace::LogMethodTraceEvent(Thread* thread, ArtMethod* method,
                                 instrumentation::Instrumentation::InstrumentationEvent event,
                                 uint32_t thread_clock_diff, uint32_t wall_clock_diff) {
diff --git a/runtime/trace.h b/runtime/trace.h
index 824b150..485e9a1 100644
--- a/runtime/trace.h
+++ b/runtime/trace.h
@@ -202,7 +202,8 @@
       // This causes the negative annotations to incorrectly have a false positive. TODO: Figure out
       // how to annotate this.
       NO_THREAD_SAFETY_ANALYSIS;
-  void FinishTracing() REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!*unique_methods_lock_);
+  void FinishTracing()
+      REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!*unique_methods_lock_, !*streaming_lock_);
 
   void ReadClocks(Thread* thread, uint32_t* thread_clock_diff, uint32_t* wall_clock_diff);
 
@@ -229,6 +230,9 @@
   // annotation.
   void WriteToBuf(const uint8_t* src, size_t src_size)
       REQUIRES(streaming_lock_);
+  // Flush the main buffer to file. Used for streaming. Exposed here for lock annotation.
+  void FlushBuf()
+      REQUIRES(streaming_lock_);
 
   uint32_t EncodeTraceMethod(ArtMethod* method) REQUIRES(!*unique_methods_lock_);
   uint32_t EncodeTraceMethodAndAction(ArtMethod* method, TraceAction action)
diff --git a/runtime/utils.cc b/runtime/utils.cc
index 8867743..410416e 100644
--- a/runtime/utils.cc
+++ b/runtime/utils.cc
@@ -788,49 +788,58 @@
   *task_cpu = strtoull(fields[36].c_str(), nullptr, 10);
 }
 
-const char* GetAndroidRoot() {
-  const char* android_root = getenv("ANDROID_ROOT");
-  if (android_root == nullptr) {
-    if (OS::DirectoryExists("/system")) {
-      android_root = "/system";
+static const char* GetAndroidDirSafe(const char* env_var,
+                                     const char* default_dir,
+                                     std::string* error_msg) {
+  const char* android_dir = getenv(env_var);
+  if (android_dir == nullptr) {
+    if (OS::DirectoryExists(default_dir)) {
+      android_dir = default_dir;
     } else {
-      LOG(FATAL) << "ANDROID_ROOT not set and /system does not exist";
-      return "";
+      *error_msg = StringPrintf("%s not set and %s does not exist", env_var, default_dir);
+      return nullptr;
     }
   }
-  if (!OS::DirectoryExists(android_root)) {
-    LOG(FATAL) << "Failed to find ANDROID_ROOT directory " << android_root;
-    return "";
+  if (!OS::DirectoryExists(android_dir)) {
+    *error_msg = StringPrintf("Failed to find %s directory %s", env_var, android_dir);
+    return nullptr;
   }
-  return android_root;
+  return android_dir;
 }
 
-const char* GetAndroidData() {
+const char* GetAndroidDir(const char* env_var, const char* default_dir) {
   std::string error_msg;
-  const char* dir = GetAndroidDataSafe(&error_msg);
+  const char* dir = GetAndroidDirSafe(env_var, default_dir, &error_msg);
   if (dir != nullptr) {
     return dir;
   } else {
     LOG(FATAL) << error_msg;
-    return "";
+    return nullptr;
   }
 }
 
+const char* GetAndroidRoot() {
+  return GetAndroidDir("ANDROID_ROOT", "/system");
+}
+
+const char* GetAndroidRootSafe(std::string* error_msg) {
+  return GetAndroidDirSafe("ANDROID_ROOT", "/system", error_msg);
+}
+
+const char* GetAndroidData() {
+  return GetAndroidDir("ANDROID_DATA", "/data");
+}
+
 const char* GetAndroidDataSafe(std::string* error_msg) {
-  const char* android_data = getenv("ANDROID_DATA");
-  if (android_data == nullptr) {
-    if (OS::DirectoryExists("/data")) {
-      android_data = "/data";
-    } else {
-      *error_msg = "ANDROID_DATA not set and /data does not exist";
-      return nullptr;
-    }
+  return GetAndroidDirSafe("ANDROID_DATA", "/data", error_msg);
+}
+
+std::string GetDefaultBootImageLocation(std::string* error_msg) {
+  const char* android_root = GetAndroidRootSafe(error_msg);
+  if (android_root == nullptr) {
+    return "";
   }
-  if (!OS::DirectoryExists(android_data)) {
-    *error_msg = StringPrintf("Failed to find ANDROID_DATA directory %s", android_data);
-    return nullptr;
-  }
-  return android_data;
+  return StringPrintf("%s/framework/boot.art", android_root);
 }
 
 void GetDalvikCache(const char* subdir, const bool create_if_absent, std::string* dalvik_cache,
diff --git a/runtime/utils.h b/runtime/utils.h
index 16ef706..9e663b3 100644
--- a/runtime/utils.h
+++ b/runtime/utils.h
@@ -143,12 +143,18 @@
 
 // Find $ANDROID_ROOT, /system, or abort.
 const char* GetAndroidRoot();
+// Find $ANDROID_ROOT, /system, or return null.
+const char* GetAndroidRootSafe(std::string* error_msg);
 
 // Find $ANDROID_DATA, /data, or abort.
 const char* GetAndroidData();
 // Find $ANDROID_DATA, /data, or return null.
 const char* GetAndroidDataSafe(std::string* error_msg);
 
+// Returns the default boot image location (ANDROID_ROOT/framework/boot.art).
+// Returns an empty string if ANDROID_ROOT is not set.
+std::string GetDefaultBootImageLocation(std::string* error_msg);
+
 // Returns the dalvik-cache location, with subdir appended. Returns the empty string if the cache
 // could not be found.
 std::string GetDalvikCache(const char* subdir);
diff --git a/runtime/verifier/verifier_deps.cc b/runtime/verifier/verifier_deps.cc
index 15cc566..1131607 100644
--- a/runtime/verifier/verifier_deps.cc
+++ b/runtime/verifier/verifier_deps.cc
@@ -963,20 +963,25 @@
   // Check recorded fields are resolved the same way, have the same recorded class,
   // and have the same recorded flags.
   ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
-  StackHandleScope<1> hs(self);
-  Handle<mirror::DexCache> dex_cache(
-      hs.NewHandle(class_linker->FindDexCache(self, dex_file, /* allow_failure */ false)));
   for (const auto& entry : fields) {
-    ArtField* field = class_linker->ResolveFieldJLS(
-        dex_file, entry.GetDexFieldIndex(), dex_cache, class_loader);
-
-    if (field == nullptr) {
-      DCHECK(self->IsExceptionPending());
-      self->ClearException();
+    const DexFile::FieldId& field_id = dex_file.GetFieldId(entry.GetDexFieldIndex());
+    StringPiece name(dex_file.StringDataByIdx(field_id.name_idx_));
+    StringPiece type(dex_file.StringDataByIdx(dex_file.GetTypeId(field_id.type_idx_).descriptor_idx_));
+    // Only use field_id.class_idx_ when the entry is unresolved, which is rare.
+    // Otherwise, we might end up resolving an application class, which is expensive.
+    std::string expected_decl_klass = entry.IsResolved()
+        ? GetStringFromId(dex_file, entry.GetDeclaringClassIndex())
+        : dex_file.StringByTypeIdx(field_id.class_idx_);
+    mirror::Class* cls = FindClassAndClearException(
+        class_linker, self, expected_decl_klass.c_str(), class_loader);
+    if (cls == nullptr) {
+      LOG(INFO) << "VerifierDeps: Could not resolve class " << expected_decl_klass;
+      return false;
     }
+    DCHECK(cls->IsResolved());
 
+    ArtField* field = mirror::Class::FindField(self, cls, name, type);
     if (entry.IsResolved()) {
-      std::string expected_decl_klass = GetStringFromId(dex_file, entry.GetDeclaringClassIndex());
       std::string temp;
       if (field == nullptr) {
         LOG(INFO) << "VerifierDeps: Could not resolve field "
@@ -1025,11 +1030,16 @@
 
     const char* name = dex_file.GetMethodName(method_id);
     const Signature signature = dex_file.GetMethodSignature(method_id);
-    const char* descriptor = dex_file.GetMethodDeclaringClassDescriptor(method_id);
+    // Only use method_id.class_idx_ when the entry is unresolved, which is rare.
+    // Otherwise, we might end up resolving an application class, which is expensive.
+    std::string expected_decl_klass = entry.IsResolved()
+        ? GetStringFromId(dex_file, entry.GetDeclaringClassIndex())
+        : dex_file.StringByTypeIdx(method_id.class_idx_);
 
-    mirror::Class* cls = FindClassAndClearException(class_linker, self, descriptor, class_loader);
+    mirror::Class* cls = FindClassAndClearException(
+        class_linker, self, expected_decl_klass.c_str(), class_loader);
     if (cls == nullptr) {
-      LOG(INFO) << "VerifierDeps: Could not resolve class " << descriptor;
+      LOG(INFO) << "VerifierDeps: Could not resolve class " << expected_decl_klass;
       return false;
     }
     DCHECK(cls->IsResolved());
@@ -1045,7 +1055,6 @@
 
     if (entry.IsResolved()) {
       std::string temp;
-      std::string expected_decl_klass = GetStringFromId(dex_file, entry.GetDeclaringClassIndex());
       if (method == nullptr) {
         LOG(INFO) << "VerifierDeps: Could not resolve "
                   << kind
diff --git a/runtime/well_known_classes.cc b/runtime/well_known_classes.cc
index 507ea16..2610252 100644
--- a/runtime/well_known_classes.cc
+++ b/runtime/well_known_classes.cc
@@ -51,7 +51,6 @@
 jclass WellKnownClasses::java_lang_ClassNotFoundException;
 jclass WellKnownClasses::java_lang_Daemons;
 jclass WellKnownClasses::java_lang_Error;
-jclass WellKnownClasses::java_lang_ExceptionInInitializerError;
 jclass WellKnownClasses::java_lang_invoke_MethodHandle;
 jclass WellKnownClasses::java_lang_IllegalAccessError;
 jclass WellKnownClasses::java_lang_NoClassDefFoundError;
@@ -290,7 +289,6 @@
   java_lang_Object = CacheClass(env, "java/lang/Object");
   java_lang_OutOfMemoryError = CacheClass(env, "java/lang/OutOfMemoryError");
   java_lang_Error = CacheClass(env, "java/lang/Error");
-  java_lang_ExceptionInInitializerError = CacheClass(env, "java/lang/ExceptionInInitializerError");
   java_lang_IllegalAccessError = CacheClass(env, "java/lang/IllegalAccessError");
   java_lang_invoke_MethodHandle = CacheClass(env, "java/lang/invoke/MethodHandle");
   java_lang_NoClassDefFoundError = CacheClass(env, "java/lang/NoClassDefFoundError");
diff --git a/runtime/well_known_classes.h b/runtime/well_known_classes.h
index b3ce3d1..db8a53c 100644
--- a/runtime/well_known_classes.h
+++ b/runtime/well_known_classes.h
@@ -61,7 +61,6 @@
   static jclass java_lang_ClassNotFoundException;
   static jclass java_lang_Daemons;
   static jclass java_lang_Error;
-  static jclass java_lang_ExceptionInInitializerError;
   static jclass java_lang_IllegalAccessError;
   static jclass java_lang_invoke_MethodHandle;
   static jclass java_lang_NoClassDefFoundError;
diff --git a/test/008-exceptions/expected.txt b/test/008-exceptions/expected.txt
index 083ecf7..fcf2ef4 100644
--- a/test/008-exceptions/expected.txt
+++ b/test/008-exceptions/expected.txt
@@ -1,11 +1,11 @@
 Got an NPE: second throw
 java.lang.NullPointerException: second throw
-	at Main.catchAndRethrow(Main.java:77)
-	at Main.exceptions_007(Main.java:59)
-	at Main.main(Main.java:67)
+	at Main.catchAndRethrow(Main.java:94)
+	at Main.exceptions_007(Main.java:74)
+	at Main.main(Main.java:82)
 Caused by: java.lang.NullPointerException: first throw
-	at Main.throwNullPointerException(Main.java:84)
-	at Main.catchAndRethrow(Main.java:74)
+	at Main.throwNullPointerException(Main.java:101)
+	at Main.catchAndRethrow(Main.java:91)
 	... 2 more
 Static Init
 BadError: This is bad by convention: BadInit
@@ -15,3 +15,11 @@
 BadErrorNoStringInit: This is bad by convention
 java.lang.NoClassDefFoundError: BadInitNoStringInit
 BadErrorNoStringInit: This is bad by convention
+BadSuperClass Static Init
+BadError: This is bad by convention: BadInit
+MultiDexBadInit Static Init
+java.lang.Error: MultiDexBadInit
+java.lang.NoClassDefFoundError: MultiDexBadInit
+  cause: java.lang.Error: MultiDexBadInit
+java.lang.NoClassDefFoundError: MultiDexBadInit
+  cause: java.lang.Error: MultiDexBadInit
diff --git a/test/008-exceptions/multidex.jpp b/test/008-exceptions/multidex.jpp
new file mode 100644
index 0000000..a3746f5
--- /dev/null
+++ b/test/008-exceptions/multidex.jpp
@@ -0,0 +1,27 @@
+BadError:
+  @@com.android.jack.annotations.ForceInMainDex
+  class BadError
+BadInit:
+  @@com.android.jack.annotations.ForceInMainDex
+  class BadInit
+BadErrorNoStringInit:
+  @@com.android.jack.annotations.ForceInMainDex
+  class BadErrorNoStringInit
+BadInitNoStringInit:
+  @@com.android.jack.annotations.ForceInMainDex
+  class BadInitNoStringInit
+BadSuperClass:
+  @@com.android.jack.annotations.ForceInMainDex
+  class BadSuperClass
+DerivedFromBadSuperClass:
+  @@com.android.jack.annotations.ForceInMainDex
+  class DerivedFromBadSuperClass
+Main:
+  @@com.android.jack.annotations.ForceInMainDex
+  class Main
+MultiDexBadInit:
+  @@com.android.jack.annotations.ForceInMainDex
+  class MultiDexBadInit
+MultiDexBadInitWrapper1:
+  @@com.android.jack.annotations.ForceInMainDex
+  class MultiDexBadInitWrapper1
diff --git a/test/008-exceptions/src-multidex/MultiDexBadInitWrapper2.java b/test/008-exceptions/src-multidex/MultiDexBadInitWrapper2.java
new file mode 100644
index 0000000..f3953bd
--- /dev/null
+++ b/test/008-exceptions/src-multidex/MultiDexBadInitWrapper2.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+class MultiDexBadInitWrapper2 {
+    public static void setDummy(int value) {
+        if (doThrow) { throw new Error(); }
+        MultiDexBadInit.dummy = value;
+    }
+
+    public static boolean doThrow = false;
+}
diff --git a/test/008-exceptions/src/Main.java b/test/008-exceptions/src/Main.java
index b8231f1..74af00c 100644
--- a/test/008-exceptions/src/Main.java
+++ b/test/008-exceptions/src/Main.java
@@ -50,6 +50,21 @@
     }
 }
 
+// A class that throws BadError during static initialization, serving as a super class.
+class BadSuperClass {
+ static int dummy;
+ static {
+     System.out.println("BadSuperClass Static Init");
+     if (true) {
+         throw new BadError("BadInit");
+     }
+ }
+}
+
+// A class that derives from BadSuperClass.
+class DerivedFromBadSuperClass extends BadSuperClass {
+}
+
 /**
  * Exceptions across method calls
  */
@@ -63,10 +78,12 @@
             npe.printStackTrace(System.out);
         }
     }
-    public static void main (String args[]) {
+    public static void main(String args[]) {
         exceptions_007();
         exceptionsRethrowClassInitFailure();
         exceptionsRethrowClassInitFailureNoStringInit();
+        exceptionsForSuperClassInitFailure();
+        exceptionsInMultiDex();
     }
 
     private static void catchAndRethrow() {
@@ -129,4 +146,70 @@
             error.printStackTrace(System.out);
         }
     }
+
+    private static void exceptionsForSuperClassInitFailure() {
+        try {
+            // Resolve DerivedFromBadSuperClass.
+            BadSuperClass.dummy = 1;
+            throw new IllegalStateException("Should not reach here.");
+        } catch (BadError e) {
+            System.out.println(e);
+        } catch (Throwable t) {
+            t.printStackTrace();
+        }
+        try {
+            // Before splitting mirror::Class::kStatusError into
+            // kStatusErrorUnresolved and kStatusErrorResolved,
+            // this would trigger a
+            //     CHECK(super_class->IsResolved())
+            // failure in
+            //     ClassLinker::LoadSuperAndInterfaces().
+            // After the change we're getting either VerifyError
+            // (for Optimizing) or NoClassDefFoundError wrapping
+            // BadError (for interpreter or JIT).
+            new DerivedFromBadSuperClass();
+            throw new IllegalStateException("Should not reach here.");
+        } catch (NoClassDefFoundError ncdfe) {
+            if (!(ncdfe.getCause() instanceof BadError)) {
+                ncdfe.getCause().printStackTrace();
+            }
+        } catch (VerifyError e) {
+        } catch (Throwable t) {
+            t.printStackTrace();
+        }
+    }
+
+    private static void exceptionsInMultiDex() {
+        try {
+            MultiDexBadInit.dummy = 1;
+            throw new IllegalStateException("Should not reach here.");
+        } catch (Error e) {
+            System.out.println(e);
+        } catch (Throwable t) {
+            t.printStackTrace();
+        }
+        // Before splitting mirror::Class::kStatusError into
+        // kStatusErrorUnresolved and kStatusErrorResolved,
+        // the exception from wrapper 1 would have been
+        // wrapped in NoClassDefFoundError but the exception
+        // from wrapper 2 would have been unwrapped.
+        try {
+            MultiDexBadInitWrapper1.setDummy(1);
+            throw new IllegalStateException("Should not reach here.");
+        } catch (NoClassDefFoundError ncdfe) {
+            System.out.println(ncdfe);
+            System.out.println("  cause: " + ncdfe.getCause());
+        } catch (Throwable t) {
+            t.printStackTrace();
+        }
+        try {
+            MultiDexBadInitWrapper2.setDummy(1);
+            throw new IllegalStateException("Should not reach here.");
+        } catch (NoClassDefFoundError ncdfe) {
+            System.out.println(ncdfe);
+            System.out.println("  cause: " + ncdfe.getCause());
+        } catch (Throwable t) {
+            t.printStackTrace();
+        }
+    }
 }
diff --git a/test/008-exceptions/src/MultiDexBadInit.java b/test/008-exceptions/src/MultiDexBadInit.java
new file mode 100644
index 0000000..e3ebb9c
--- /dev/null
+++ b/test/008-exceptions/src/MultiDexBadInit.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+class MultiDexBadInit {
+    static int dummy;
+    static {
+        System.out.println("MultiDexBadInit Static Init");
+        if (true) {
+            throw new Error("MultiDexBadInit");
+        }
+    }
+}
diff --git a/test/008-exceptions/src/MultiDexBadInitWrapper1.java b/test/008-exceptions/src/MultiDexBadInitWrapper1.java
new file mode 100644
index 0000000..059e6a3
--- /dev/null
+++ b/test/008-exceptions/src/MultiDexBadInitWrapper1.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+class MultiDexBadInitWrapper1 {
+    public static void setDummy(int value) {
+        if (doThrow) { throw new Error(); }
+        MultiDexBadInit.dummy = value;
+    }
+
+    public static boolean doThrow = false;
+}
diff --git a/test/142-classloader2/expected.txt b/test/142-classloader2/expected.txt
index 86f5e22..056d978 100644
--- a/test/142-classloader2/expected.txt
+++ b/test/142-classloader2/expected.txt
@@ -1 +1,5 @@
+Loaded class B.
+Caught VerifyError.
+Loaded class B.
+Caught wrapped VerifyError.
 Everything OK.
diff --git a/test/142-classloader2/src/Main.java b/test/142-classloader2/src/Main.java
index 80b00e7..a0c7764 100644
--- a/test/142-classloader2/src/Main.java
+++ b/test/142-classloader2/src/Main.java
@@ -74,16 +74,25 @@
         // Try to load a dex file with bad dex code. Use new instance to force verification.
         try {
           Class<?> badClass = Main.class.getClassLoader().loadClass("B");
+          System.out.println("Loaded class B.");
           badClass.newInstance();
-          System.out.println("Should not be able to load class from bad dex file.");
+          System.out.println("Should not be able to instantiate B with bad dex bytecode.");
         } catch (VerifyError e) {
+          System.out.println("Caught VerifyError.");
         }
 
         // Make sure the same error is rethrown when reloading the bad class.
         try {
           Class<?> badClass = Main.class.getClassLoader().loadClass("B");
-          System.out.println("Should not be able to load class from bad dex file.");
-        } catch (VerifyError e) {
+          System.out.println("Loaded class B.");
+          badClass.newInstance();
+          System.out.println("Should not be able to instantiate B with bad dex bytecode.");
+        } catch (NoClassDefFoundError e) {
+          if (e.getCause() instanceof VerifyError) {
+            System.out.println("Caught wrapped VerifyError.");
+          } else {
+            e.printStackTrace();
+          }
         }
 
         System.out.println("Everything OK.");
diff --git a/test/154-gc-loop/expected.txt b/test/154-gc-loop/expected.txt
new file mode 100644
index 0000000..6106818
--- /dev/null
+++ b/test/154-gc-loop/expected.txt
@@ -0,0 +1,2 @@
+JNI_OnLoad called
+Finalize count too large: false
diff --git a/test/154-gc-loop/heap_interface.cc b/test/154-gc-loop/heap_interface.cc
new file mode 100644
index 0000000..8d610a8
--- /dev/null
+++ b/test/154-gc-loop/heap_interface.cc
@@ -0,0 +1,28 @@
+/*
+ * 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 "gc/heap.h"
+#include "runtime.h"
+
+namespace art {
+namespace {
+
+extern "C" JNIEXPORT void JNICALL Java_Main_backgroundProcessState(JNIEnv*, jclass) {
+  Runtime::Current()->UpdateProcessState(kProcessStateJankImperceptible);
+}
+
+}  // namespace
+}  // namespace art
diff --git a/test/154-gc-loop/info.txt b/test/154-gc-loop/info.txt
new file mode 100644
index 0000000..f599db1
--- /dev/null
+++ b/test/154-gc-loop/info.txt
@@ -0,0 +1 @@
+Test that GC doesn't happen too often for a few small allocations.
diff --git a/test/154-gc-loop/src/Main.java b/test/154-gc-loop/src/Main.java
new file mode 100644
index 0000000..3a256c1
--- /dev/null
+++ b/test/154-gc-loop/src/Main.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.lang.ref.WeakReference;
+
+public class Main {
+  static final class GcWatcher {
+    protected void finalize() throws Throwable {
+        watcher = new WeakReference<GcWatcher>(new GcWatcher());
+        ++finalizeCounter;
+    }
+  }
+  static WeakReference<GcWatcher> watcher = new WeakReference<GcWatcher>(new GcWatcher());
+  static Object o = new Object();
+  static int finalizeCounter = 0;
+
+  public static void main(String[] args) {
+    System.loadLibrary(args[0]);
+    backgroundProcessState();
+    try {
+        Runtime.getRuntime().gc();
+        for (int i = 0; i < 10; ++i) {
+            o = new Object();
+            Thread.sleep(1000);
+        }
+    } catch (Exception e) {}
+    System.out.println("Finalize count too large: " +
+            ((finalizeCounter >= 10) ? Integer.toString(finalizeCounter) : "false"));
+  }
+
+  private static native void backgroundProcessState();
+}
diff --git a/test/564-checker-irreducible-loop/smali/IrreducibleLoop.smali b/test/564-checker-irreducible-loop/smali/IrreducibleLoop.smali
index 75344f7..e4bf236 100644
--- a/test/564-checker-irreducible-loop/smali/IrreducibleLoop.smali
+++ b/test/564-checker-irreducible-loop/smali/IrreducibleLoop.smali
@@ -17,10 +17,9 @@
 .super Ljava/lang/Object;
 
 ## CHECK-START-X86: int IrreducibleLoop.simpleLoop(int) dead_code_elimination$initial (before)
-## CHECK-DAG: <<Method:(i|j)\d+>> CurrentMethod
 ## CHECK-DAG: <<Constant:i\d+>>   IntConstant 42
-## CHECK-DAG:                     InvokeStaticOrDirect [<<Constant>>,<<Method>>] loop:{{B\d+}} irreducible:true
-## CHECK-DAG:                     InvokeStaticOrDirect [<<Constant>>,<<Method>>] loop:none
+## CHECK-DAG:                     InvokeStaticOrDirect [<<Constant>>] loop:{{B\d+}} irreducible:true
+## CHECK-DAG:                     InvokeStaticOrDirect [<<Constant>>] loop:none
 .method public static simpleLoop(I)I
    .registers 3
    const/16 v0, 42
diff --git a/test/588-checker-irreducib-lifetime-hole/smali/IrreducibleLoop.smali b/test/588-checker-irreducib-lifetime-hole/smali/IrreducibleLoop.smali
index 186f0ab..9b8aa51 100644
--- a/test/588-checker-irreducib-lifetime-hole/smali/IrreducibleLoop.smali
+++ b/test/588-checker-irreducib-lifetime-hole/smali/IrreducibleLoop.smali
@@ -17,11 +17,10 @@
 .super Ljava/lang/Object;
 
 ## CHECK-START-X86: int IrreducibleLoop.simpleLoop1(int) dead_code_elimination$initial (before)
-## CHECK-DAG: <<Method:(i|j)\d+>> CurrentMethod
 ## CHECK-DAG: <<Constant:i\d+>>   IntConstant 42
 ## CHECK-DAG:                     Goto irreducible:true
-## CHECK-DAG:                     InvokeStaticOrDirect [<<Constant>>,<<Method>>] loop:none
-## CHECK-DAG:                     InvokeStaticOrDirect [{{i\d+}},<<Method>>] loop:none
+## CHECK-DAG:                     InvokeStaticOrDirect [<<Constant>>] loop:none
+## CHECK-DAG:                     InvokeStaticOrDirect [{{i\d+}}] loop:none
 .method public static simpleLoop1(I)I
    .registers 3
    const/16 v0, 42
@@ -58,11 +57,10 @@
 .end method
 
 ## CHECK-START-X86: int IrreducibleLoop.simpleLoop2(int) dead_code_elimination$initial (before)
-## CHECK-DAG: <<Method:(i|j)\d+>> CurrentMethod
 ## CHECK-DAG: <<Constant:i\d+>>   IntConstant 42
 ## CHECK-DAG:                     Goto irreducible:true
-## CHECK-DAG:                     InvokeStaticOrDirect [<<Constant>>,<<Method>>] loop:none
-## CHECK-DAG:                     InvokeStaticOrDirect [{{i\d+}},<<Method>>] loop:none
+## CHECK-DAG:                     InvokeStaticOrDirect [<<Constant>>] loop:none
+## CHECK-DAG:                     InvokeStaticOrDirect [{{i\d+}}] loop:none
 .method public static simpleLoop2(I)I
    .registers 3
    const/16 v0, 42
diff --git a/test/595-profile-saving/profile-saving.cc b/test/595-profile-saving/profile-saving.cc
index bf3d812..0f8dd57 100644
--- a/test/595-profile-saving/profile-saving.cc
+++ b/test/595-profile-saving/profile-saving.cc
@@ -17,7 +17,7 @@
 #include "dex_file.h"
 
 #include "art_method-inl.h"
-#include "jit/offline_profiling_info.h"
+#include "jit/profile_compilation_info.h"
 #include "jit/profile_saver.h"
 #include "jni.h"
 #include "method_reference.h"
diff --git a/test/596-monitor-inflation/expected.txt b/test/596-monitor-inflation/expected.txt
new file mode 100644
index 0000000..2add696
--- /dev/null
+++ b/test/596-monitor-inflation/expected.txt
@@ -0,0 +1,6 @@
+JNI_OnLoad called
+Monitor list grew by at least 4000 monitors
+Monitor list shrank correctly
+Finished first check
+Finished second check
+Total checks: 10000
diff --git a/test/596-monitor-inflation/info.txt b/test/596-monitor-inflation/info.txt
new file mode 100644
index 0000000..81dedb6
--- /dev/null
+++ b/test/596-monitor-inflation/info.txt
@@ -0,0 +1,5 @@
+A simple test that forces many monitors to be inflated, while checking
+that hashcodes are consistently maintained.
+
+This allocates more monitors and hence may exercise the monitor pool
+differently, and with more context, than the monitor_pool_test gtest.
diff --git a/test/596-monitor-inflation/monitor_inflation.cc b/test/596-monitor-inflation/monitor_inflation.cc
new file mode 100644
index 0000000..fb4275b
--- /dev/null
+++ b/test/596-monitor-inflation/monitor_inflation.cc
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "gc/heap.h"
+#include "jni.h"
+#include "monitor.h"
+#include "runtime.h"
+#include "thread-inl.h"
+
+namespace art {
+namespace {
+
+extern "C" JNIEXPORT void JNICALL Java_Main_trim(JNIEnv*, jclass) {
+  Runtime::Current()->GetHeap()->Trim(Thread::Current());
+}
+
+extern "C" JNIEXPORT jint JNICALL Java_Main_monitorListSize(JNIEnv*, jclass) {
+  return Runtime::Current()->GetMonitorList()->Size();
+}
+
+}  // namespace
+}  // namespace art
diff --git a/test/596-monitor-inflation/src/Main.java b/test/596-monitor-inflation/src/Main.java
new file mode 100644
index 0000000..d97c766
--- /dev/null
+++ b/test/596-monitor-inflation/src/Main.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import java.util.IdentityHashMap;
+import dalvik.system.VMRuntime;
+
+public class Main {
+  public static void main(String[] args) {
+    System.loadLibrary(args[0]);
+    int initialSize = monitorListSize();
+    IdentityHashMap<Object, Integer> all = new IdentityHashMap();
+    for (int i = 0; i < 5000; ++i) {
+      Object obj = new Object();
+      synchronized(obj) {
+        // Should force inflation.
+        all.put(obj, obj.hashCode());
+      }
+    }
+    // Since monitor deflation is delayed significantly, we believe that even with an intervening
+    // GC, monitors should remain inflated.  We allow some slop for unrelated concurrent runtime
+    // actions.
+    int inflatedSize = monitorListSize();
+    if (inflatedSize >= initialSize + 4000) {
+        System.out.println("Monitor list grew by at least 4000 monitors");
+    } else {
+        System.out.println("Monitor list did not grow as expected");
+    }
+    // Encourage monitor deflation.
+    // trim() (Heap::Trim()) deflates only in JANK_IMPERCEPTIBLE state.
+    // Some of this mirrors code in ActivityThread.java.
+    final int DALVIK_PROCESS_STATE_JANK_PERCEPTIBLE = 0;
+    final int DALVIK_PROCESS_STATE_JANK_IMPERCEPTIBLE = 1;
+    VMRuntime.getRuntime().updateProcessState(DALVIK_PROCESS_STATE_JANK_IMPERCEPTIBLE);
+    System.gc();
+    System.runFinalization();
+    trim();
+    VMRuntime.getRuntime().updateProcessState(DALVIK_PROCESS_STATE_JANK_PERCEPTIBLE);
+    int finalSize = monitorListSize();
+    if (finalSize > initialSize + 1000) {
+        System.out.println("Monitor list failed to shrink properly");
+    } else {
+        System.out.println("Monitor list shrank correctly");
+    }
+    int j = 0;
+    for (Object obj: all.keySet()) {
+      ++j;
+      if (obj.hashCode() != all.get(obj)) {
+        throw new AssertionError("Failed hashcode test!");
+      }
+    }
+    System.out.println("Finished first check");
+    for (Object obj: all.keySet()) {
+      ++j;
+      synchronized(obj) {
+        if (obj.hashCode() != all.get(obj)) {
+          throw new AssertionError("Failed hashcode test!");
+        }
+      }
+    }
+    System.out.println("Finished second check");
+    System.out.println("Total checks: " + j);
+  }
+
+  private static native void trim();
+
+  private static native int monitorListSize();
+}
diff --git a/test/901-hello-ti-agent/basics.cc b/test/901-hello-ti-agent/basics.cc
index 052fb9a..0b17656 100644
--- a/test/901-hello-ti-agent/basics.cc
+++ b/test/901-hello-ti-agent/basics.cc
@@ -28,6 +28,46 @@
 namespace art {
 namespace Test901HelloTi {
 
+static void EnableEvent(jvmtiEnv* env, jvmtiEvent evt) {
+  jvmtiError error = env->SetEventNotificationMode(JVMTI_ENABLE, evt, nullptr);
+  if (error != JVMTI_ERROR_NONE) {
+    printf("Failed to enable event");
+  }
+}
+
+static void JNICALL VMStartCallback(jvmtiEnv *jenv ATTRIBUTE_UNUSED,
+                                     JNIEnv* jni_env ATTRIBUTE_UNUSED) {
+  printf("VMStart\n");
+}
+
+static void JNICALL VMInitCallback(jvmtiEnv *jvmti_env ATTRIBUTE_UNUSED,
+                                   JNIEnv* jni_env ATTRIBUTE_UNUSED,
+                                   jthread thread ATTRIBUTE_UNUSED) {
+  printf("VMInit\n");
+}
+
+static void JNICALL VMDeatchCallback(jvmtiEnv *jenv ATTRIBUTE_UNUSED,
+                                     JNIEnv* jni_env ATTRIBUTE_UNUSED) {
+  printf("VMDeath\n");
+}
+
+
+static void InstallVMEvents(jvmtiEnv* env) {
+  jvmtiEventCallbacks callbacks;
+  memset(&callbacks, 0, sizeof(jvmtiEventCallbacks));
+  callbacks.VMStart = VMStartCallback;
+  callbacks.VMInit = VMInitCallback;
+  callbacks.VMDeath = VMDeatchCallback;
+  jvmtiError ret = env->SetEventCallbacks(&callbacks, sizeof(callbacks));
+  if (ret != JVMTI_ERROR_NONE) {
+    printf("Failed to install callbacks");
+  }
+
+  EnableEvent(env, JVMTI_EVENT_VM_START);
+  EnableEvent(env, JVMTI_EVENT_VM_INIT);
+  EnableEvent(env, JVMTI_EVENT_VM_DEATH);
+}
+
 jint OnLoad(JavaVM* vm,
             char* options ATTRIBUTE_UNUSED,
             void* reserved ATTRIBUTE_UNUSED) {
@@ -72,6 +112,10 @@
     printf("Unexpected version number!\n");
     return -1;
   }
+
+  InstallVMEvents(env);
+  InstallVMEvents(env2);
+
   CHECK_CALL_SUCCESS(env->DisposeEnvironment());
   CHECK_CALL_SUCCESS(env2->DisposeEnvironment());
 #undef CHECK_CALL_SUCCESS
@@ -82,6 +126,19 @@
   }
   SetAllCapabilities(jvmti_env);
 
+  jvmtiPhase current_phase;
+  jvmtiError phase_result = jvmti_env->GetPhase(&current_phase);
+  if (phase_result != JVMTI_ERROR_NONE) {
+    printf("Could not get phase");
+    return 1;
+  }
+  if (current_phase != JVMTI_PHASE_ONLOAD) {
+    printf("Wrong phase");
+    return 1;
+  }
+
+  InstallVMEvents(jvmti_env);
+
   return JNI_OK;
 }
 
@@ -92,5 +149,15 @@
   JvmtiErrorToException(env, result);
 }
 
+extern "C" JNIEXPORT jboolean JNICALL Java_Main_checkLivePhase(
+    JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED) {
+  jvmtiPhase current_phase;
+  jvmtiError phase_result = jvmti_env->GetPhase(&current_phase);
+  if (JvmtiErrorToException(env, phase_result)) {
+    return JNI_FALSE;
+  }
+  return (current_phase == JVMTI_PHASE_LIVE) ? JNI_TRUE : JNI_FALSE;
+}
+
 }  // namespace Test901HelloTi
 }  // namespace art
diff --git a/test/901-hello-ti-agent/expected.txt b/test/901-hello-ti-agent/expected.txt
index 2aee99b..c4b24cb 100644
--- a/test/901-hello-ti-agent/expected.txt
+++ b/test/901-hello-ti-agent/expected.txt
@@ -1,8 +1,12 @@
 Loaded Agent for test 901-hello-ti-agent
+VMStart
+VMInit
 Hello, world!
+Agent in live phase.
 0
 1
 2
 4
 8
 JVMTI_ERROR_ILLEGAL_ARGUMENT
+VMDeath
diff --git a/test/901-hello-ti-agent/src/Main.java b/test/901-hello-ti-agent/src/Main.java
index 775e5c2..4d62ed3 100644
--- a/test/901-hello-ti-agent/src/Main.java
+++ b/test/901-hello-ti-agent/src/Main.java
@@ -16,10 +16,12 @@
 
 public class Main {
   public static void main(String[] args) {
-    System.loadLibrary(args[1]);
-
     System.out.println("Hello, world!");
 
+    if (checkLivePhase()) {
+      System.out.println("Agent in live phase.");
+    }
+
     set(0);  // OTHER
     set(1);  // GC
     set(2);  // CLASS
@@ -37,5 +39,6 @@
     }
   }
 
+  private static native boolean checkLivePhase();
   private static native void setVerboseFlag(int flag, boolean value);
 }
diff --git a/test/902-hello-transformation/src/Main.java b/test/902-hello-transformation/src/Main.java
index ec47119..471c82b 100644
--- a/test/902-hello-transformation/src/Main.java
+++ b/test/902-hello-transformation/src/Main.java
@@ -49,7 +49,6 @@
     "AgAAABMCAAAAIAAAAQAAAB4CAAAAEAAAAQAAACwCAAA=");
 
   public static void main(String[] args) {
-    System.loadLibrary(args[1]);
     doTest(new Transform());
   }
 
diff --git a/test/903-hello-tagging/src/Main.java b/test/903-hello-tagging/src/Main.java
index a8aedb4..2f0365a 100644
--- a/test/903-hello-tagging/src/Main.java
+++ b/test/903-hello-tagging/src/Main.java
@@ -20,7 +20,6 @@
 
 public class Main {
   public static void main(String[] args) {
-    System.loadLibrary(args[1]);
     doTest();
     testGetTaggedObjects();
   }
diff --git a/test/904-object-allocation/src/Main.java b/test/904-object-allocation/src/Main.java
index fc8a112..df59179 100644
--- a/test/904-object-allocation/src/Main.java
+++ b/test/904-object-allocation/src/Main.java
@@ -18,8 +18,6 @@
 
 public class Main {
   public static void main(String[] args) throws Exception {
-    System.loadLibrary(args[1]);
-
     // Use a list to ensure objects must be allocated.
     ArrayList<Object> l = new ArrayList<>(100);
 
diff --git a/test/905-object-free/src/Main.java b/test/905-object-free/src/Main.java
index 16dec5d..e41e378 100644
--- a/test/905-object-free/src/Main.java
+++ b/test/905-object-free/src/Main.java
@@ -19,8 +19,6 @@
 
 public class Main {
   public static void main(String[] args) throws Exception {
-    System.loadLibrary(args[1]);
-
     doTest();
   }
 
diff --git a/test/906-iterate-heap/src/Main.java b/test/906-iterate-heap/src/Main.java
index 544a365..cab27be 100644
--- a/test/906-iterate-heap/src/Main.java
+++ b/test/906-iterate-heap/src/Main.java
@@ -19,8 +19,6 @@
 
 public class Main {
   public static void main(String[] args) throws Exception {
-    System.loadLibrary(args[1]);
-
     doTest();
   }
 
diff --git a/test/907-get-loaded-classes/src/Main.java b/test/907-get-loaded-classes/src/Main.java
index 468d037..370185a 100644
--- a/test/907-get-loaded-classes/src/Main.java
+++ b/test/907-get-loaded-classes/src/Main.java
@@ -20,8 +20,6 @@
 
 public class Main {
   public static void main(String[] args) throws Exception {
-    System.loadLibrary(args[1]);
-
     doTest();
   }
 
diff --git a/test/908-gc-start-finish/src/Main.java b/test/908-gc-start-finish/src/Main.java
index 2be0eea..05388c9 100644
--- a/test/908-gc-start-finish/src/Main.java
+++ b/test/908-gc-start-finish/src/Main.java
@@ -18,8 +18,6 @@
 
 public class Main {
   public static void main(String[] args) throws Exception {
-    System.loadLibrary(args[1]);
-
     doTest();
   }
 
diff --git a/test/910-methods/src/Main.java b/test/910-methods/src/Main.java
index bf25a0d..932a1ea 100644
--- a/test/910-methods/src/Main.java
+++ b/test/910-methods/src/Main.java
@@ -20,8 +20,6 @@
 
 public class Main {
   public static void main(String[] args) throws Exception {
-    System.loadLibrary(args[1]);
-
     doTest();
   }
 
diff --git a/test/911-get-stack-trace/expected.txt b/test/911-get-stack-trace/expected.txt
index dad08c9..2687f85 100644
--- a/test/911-get-stack-trace/expected.txt
+++ b/test/911-get-stack-trace/expected.txt
@@ -22,7 +22,7 @@
  bar (IIILControlData;)J 0 24
  foo (IIILControlData;)I 0 19
  doTest ()V 38 23
- main ([Ljava/lang/String;)V 6 21
+ main ([Ljava/lang/String;)V 3 21
 ---------
  print (Ljava/lang/Thread;II)V 0 34
  printOrWait (IILControlData;)V 6 39
@@ -42,7 +42,7 @@
  bar (IIILControlData;)J 0 24
  foo (IIILControlData;)I 0 19
  doTest ()V 42 24
- main ([Ljava/lang/String;)V 6 21
+ main ([Ljava/lang/String;)V 3 21
 ---------
  getStackTrace (Ljava/lang/Thread;II)[[Ljava/lang/String; -1 -2
  print (Ljava/lang/Thread;II)V 0 34
@@ -57,13 +57,13 @@
  baz (IIILControlData;)Ljava/lang/Object; 9 32
 From bottom
 ---------
- main ([Ljava/lang/String;)V 6 21
+ main ([Ljava/lang/String;)V 3 21
 ---------
  baz (IIILControlData;)Ljava/lang/Object; 9 32
  bar (IIILControlData;)J 0 24
  foo (IIILControlData;)I 0 19
  doTest ()V 65 30
- main ([Ljava/lang/String;)V 6 21
+ main ([Ljava/lang/String;)V 3 21
 ---------
  bar (IIILControlData;)J 0 24
  foo (IIILControlData;)I 0 19
@@ -358,7 +358,7 @@
  getAllStackTraces (I)[[Ljava/lang/Object; -1 -2
  printAll (I)V 0 73
  doTest ()V 102 57
- main ([Ljava/lang/String;)V 30 33
+ main ([Ljava/lang/String;)V 27 33
 
 ---------
 FinalizerDaemon
@@ -590,7 +590,7 @@
  getAllStackTraces (I)[[Ljava/lang/Object; -1 -2
  printAll (I)V 0 73
  doTest ()V 107 59
- main ([Ljava/lang/String;)V 30 33
+ main ([Ljava/lang/String;)V 27 33
 
 
 ########################################
@@ -659,7 +659,7 @@
  getThreadListStackTraces ([Ljava/lang/Thread;I)[[Ljava/lang/Object; -1 -2
  printList ([Ljava/lang/Thread;I)V 0 66
  doTest ()V 96 52
- main ([Ljava/lang/String;)V 38 37
+ main ([Ljava/lang/String;)V 35 37
 
 ---------
 Thread-14
@@ -771,7 +771,7 @@
  getThreadListStackTraces ([Ljava/lang/Thread;I)[[Ljava/lang/Object; -1 -2
  printList ([Ljava/lang/Thread;I)V 0 66
  doTest ()V 101 54
- main ([Ljava/lang/String;)V 38 37
+ main ([Ljava/lang/String;)V 35 37
 
 
 ###################
@@ -782,7 +782,7 @@
 [public static native java.lang.Object[] Frames.getFrameLocation(java.lang.Thread,int), ffffffff]
 [public static void Frames.doTestSameThread(), 38]
 [public static void Frames.doTest() throws java.lang.Exception, 0]
-[public static void Main.main(java.lang.String[]) throws java.lang.Exception, 2e]
+[public static void Main.main(java.lang.String[]) throws java.lang.Exception, 2b]
 JVMTI_ERROR_NO_MORE_FRAMES
 
 ################################
diff --git a/test/911-get-stack-trace/src/Main.java b/test/911-get-stack-trace/src/Main.java
index b199033..96a427d 100644
--- a/test/911-get-stack-trace/src/Main.java
+++ b/test/911-get-stack-trace/src/Main.java
@@ -16,7 +16,7 @@
 
 public class Main {
   public static void main(String[] args) throws Exception {
-    System.loadLibrary(args[1]);
+    bindTest911Classes();
 
     SameThread.doTest();
 
@@ -42,4 +42,6 @@
 
     System.out.println("Done");
   }
+
+  private static native void bindTest911Classes();
 }
diff --git a/test/911-get-stack-trace/stack_trace.cc b/test/911-get-stack-trace/stack_trace.cc
index d162e8a..68f6d8d 100644
--- a/test/911-get-stack-trace/stack_trace.cc
+++ b/test/911-get-stack-trace/stack_trace.cc
@@ -34,6 +34,14 @@
 
 using android::base::StringPrintf;
 
+extern "C" JNIEXPORT void JNICALL Java_Main_bindTest911Classes(
+    JNIEnv* env, jclass klass ATTRIBUTE_UNUSED) {
+  BindFunctions(jvmti_env, env, "AllTraces");
+  BindFunctions(jvmti_env, env, "Frames");
+  BindFunctions(jvmti_env, env, "PrintThread");
+  BindFunctions(jvmti_env, env, "ThreadListTraces");
+}
+
 static jint FindLineNumber(jint line_number_count,
                            jvmtiLineNumberEntry* line_number_table,
                            jlocation location) {
diff --git a/test/912-classes/classes.cc b/test/912-classes/classes.cc
index a22d1d7..d13436e 100644
--- a/test/912-classes/classes.cc
+++ b/test/912-classes/classes.cc
@@ -20,6 +20,7 @@
 #include "jni.h"
 #include "openjdkjvmti/jvmti.h"
 #include "ScopedLocalRef.h"
+#include "thread-inl.h"
 
 #include "ti-agent/common_helper.h"
 #include "ti-agent/common_load.h"
@@ -241,5 +242,138 @@
   return ret;
 }
 
+extern "C" JNIEXPORT jintArray JNICALL Java_Main_getClassVersion(
+    JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED, jclass klass) {
+  jint major, minor;
+  jvmtiError result = jvmti_env->GetClassVersionNumbers(klass, &minor, &major);
+  if (JvmtiErrorToException(env, result)) {
+    return nullptr;
+  }
+
+  jintArray int_array = env->NewIntArray(2);
+  if (int_array == nullptr) {
+    return nullptr;
+  }
+  jint buf[2] = { major, minor };
+  env->SetIntArrayRegion(int_array, 0, 2, buf);
+
+  return int_array;
+}
+
+static std::string GetClassName(jvmtiEnv* jenv, JNIEnv* jni_env, jclass klass) {
+  char* name;
+  jvmtiError result = jenv->GetClassSignature(klass, &name, nullptr);
+  if (result != JVMTI_ERROR_NONE) {
+    if (jni_env != nullptr) {
+      JvmtiErrorToException(jni_env, result);
+    } else {
+      printf("Failed to get class signature.\n");
+    }
+    return "";
+  }
+
+  std::string tmp(name);
+  jenv->Deallocate(reinterpret_cast<unsigned char*>(name));
+
+  return tmp;
+}
+
+static std::string GetThreadName(jvmtiEnv* jenv, JNIEnv* jni_env, jthread thread) {
+  jvmtiThreadInfo info;
+  jvmtiError result = jenv->GetThreadInfo(thread, &info);
+  if (result != JVMTI_ERROR_NONE) {
+    if (jni_env != nullptr) {
+      JvmtiErrorToException(jni_env, result);
+    } else {
+      printf("Failed to get thread name.\n");
+    }
+    return "";
+  }
+
+  std::string tmp(info.name);
+  jenv->Deallocate(reinterpret_cast<unsigned char*>(info.name));
+  jni_env->DeleteLocalRef(info.context_class_loader);
+  jni_env->DeleteLocalRef(info.thread_group);
+
+  return tmp;
+}
+
+static std::string GetThreadName(Thread* thread) {
+  std::string tmp;
+  thread->GetThreadName(tmp);
+  return tmp;
+}
+
+static void JNICALL ClassPrepareCallback(jvmtiEnv* jenv,
+                                         JNIEnv* jni_env,
+                                         jthread thread,
+                                         jclass klass) {
+  std::string name = GetClassName(jenv, jni_env, klass);
+  if (name == "") {
+    return;
+  }
+  std::string thread_name = GetThreadName(jenv, jni_env, thread);
+  if (thread_name == "") {
+    return;
+  }
+  std::string cur_thread_name = GetThreadName(Thread::Current());
+  printf("Prepare: %s on %s (cur=%s)\n",
+         name.c_str(),
+         thread_name.c_str(),
+         cur_thread_name.c_str());
+}
+
+static void JNICALL ClassLoadCallback(jvmtiEnv* jenv,
+                                      JNIEnv* jni_env,
+                                      jthread thread,
+                                      jclass klass) {
+  std::string name = GetClassName(jenv, jni_env, klass);
+  if (name == "") {
+    return;
+  }
+  std::string thread_name = GetThreadName(jenv, jni_env, thread);
+  if (thread_name == "") {
+    return;
+  }
+  printf("Load: %s on %s\n", name.c_str(), thread_name.c_str());
+}
+
+extern "C" JNIEXPORT void JNICALL Java_Main_enableClassLoadEvents(
+    JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED, jboolean b) {
+  if (b == JNI_FALSE) {
+    jvmtiError ret = jvmti_env->SetEventNotificationMode(JVMTI_DISABLE,
+                                                         JVMTI_EVENT_CLASS_LOAD,
+                                                         nullptr);
+    if (JvmtiErrorToException(env, ret)) {
+      return;
+    }
+    ret = jvmti_env->SetEventNotificationMode(JVMTI_DISABLE,
+                                              JVMTI_EVENT_CLASS_PREPARE,
+                                              nullptr);
+    JvmtiErrorToException(env, ret);
+    return;
+  }
+
+  jvmtiEventCallbacks callbacks;
+  memset(&callbacks, 0, sizeof(jvmtiEventCallbacks));
+  callbacks.ClassLoad = ClassLoadCallback;
+  callbacks.ClassPrepare = ClassPrepareCallback;
+  jvmtiError ret = jvmti_env->SetEventCallbacks(&callbacks, sizeof(callbacks));
+  if (JvmtiErrorToException(env, ret)) {
+    return;
+  }
+
+  ret = jvmti_env->SetEventNotificationMode(JVMTI_ENABLE,
+                                            JVMTI_EVENT_CLASS_LOAD,
+                                            nullptr);
+  if (JvmtiErrorToException(env, ret)) {
+    return;
+  }
+  ret = jvmti_env->SetEventNotificationMode(JVMTI_ENABLE,
+                                            JVMTI_EVENT_CLASS_PREPARE,
+                                            nullptr);
+  JvmtiErrorToException(env, ret);
+}
+
 }  // namespace Test912Classes
 }  // namespace art
diff --git a/test/912-classes/expected.txt b/test/912-classes/expected.txt
index a95a465..328216b 100644
--- a/test/912-classes/expected.txt
+++ b/test/912-classes/expected.txt
@@ -29,7 +29,7 @@
 class [Ljava.lang.String; 10000
 class java.lang.Object 111
 class Main$TestForNonInit 11
-class Main$TestForInitFail 1001
+class Main$TestForInitFail 1011
 int []
 class [Ljava.lang.String; []
 class java.lang.Object []
@@ -59,3 +59,35 @@
 boot <- src+src-ex (A,B)
 912-classes.jar+ -> 
 [class A, class B, class java.lang.Object]
+
+[37, 0]
+
+B, false
+Load: LB; on main
+Prepare: LB; on main (cur=main)
+B, true
+Load: LB; on main
+Prepare: LB; on main (cur=main)
+C, false
+Load: LA; on main
+Prepare: LA; on main (cur=main)
+Load: LC; on main
+Prepare: LC; on main (cur=main)
+A, false
+C, true
+Load: LA; on main
+Prepare: LA; on main (cur=main)
+Load: LC; on main
+Prepare: LC; on main (cur=main)
+A, true
+A, true
+Load: LA; on main
+Prepare: LA; on main (cur=main)
+C, true
+Load: LC; on main
+Prepare: LC; on main (cur=main)
+C, true
+Load: LA; on TestRunner
+Prepare: LA; on TestRunner (cur=TestRunner)
+Load: LC; on TestRunner
+Prepare: LC; on TestRunner (cur=TestRunner)
diff --git a/test/912-classes/src-ex/A.java b/test/912-classes/src-ex/A.java
index 64acb2f..2c43cfb 100644
--- a/test/912-classes/src-ex/A.java
+++ b/test/912-classes/src-ex/A.java
@@ -15,4 +15,4 @@
  */
 
 public class A {
-}
\ No newline at end of file
+}
diff --git a/test/912-classes/src-ex/C.java b/test/912-classes/src-ex/C.java
new file mode 100644
index 0000000..97f8021
--- /dev/null
+++ b/test/912-classes/src-ex/C.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+public class C extends A {
+}
diff --git a/test/912-classes/src/B.java b/test/912-classes/src/B.java
index f1458c3..52ce4dd 100644
--- a/test/912-classes/src/B.java
+++ b/test/912-classes/src/B.java
@@ -15,4 +15,4 @@
  */
 
 public class B {
-}
\ No newline at end of file
+}
diff --git a/test/912-classes/src/Main.java b/test/912-classes/src/Main.java
index ea3c49c..6ad23a4 100644
--- a/test/912-classes/src/Main.java
+++ b/test/912-classes/src/Main.java
@@ -21,8 +21,6 @@
 
 public class Main {
   public static void main(String[] args) throws Exception {
-    System.loadLibrary(args[1]);
-
     doTest();
   }
 
@@ -80,6 +78,14 @@
     testClassLoader(getProxyClass());
 
     testClassLoaderClasses();
+
+    System.out.println();
+
+    testClassVersion();
+
+    System.out.println();
+
+    testClassEvents();
   }
 
   private static Class<?> proxyClass = null;
@@ -202,6 +208,71 @@
     }
   }
 
+  private static void testClassVersion() {
+    System.out.println(Arrays.toString(getClassVersion(Main.class)));
+  }
+
+  private static void testClassEvents() throws Exception {
+    ClassLoader cl = Main.class.getClassLoader();
+    while (cl.getParent() != null) {
+      cl = cl.getParent();
+    }
+    final ClassLoader boot = cl;
+
+    Runnable r = new Runnable() {
+      @Override
+      public void run() {
+        try {
+          ClassLoader cl6 = create(boot, DEX1, DEX2);
+          System.out.println("C, true");
+          Class.forName("C", true, cl6);
+        } catch (Exception e) {
+          throw new RuntimeException(e);
+        }
+      }
+    };
+
+    Thread dummyThread = new Thread();
+    dummyThread.start();
+    dummyThread.join();
+
+    ensureJitCompiled(Main.class, "testClassEvents");
+
+    enableClassLoadEvents(true);
+
+    ClassLoader cl1 = create(boot, DEX1, DEX2);
+    System.out.println("B, false");
+    Class.forName("B", false, cl1);
+
+    ClassLoader cl2 = create(boot, DEX1, DEX2);
+    System.out.println("B, true");
+    Class.forName("B", true, cl2);
+
+    ClassLoader cl3 = create(boot, DEX1, DEX2);
+    System.out.println("C, false");
+    Class.forName("C", false, cl3);
+    System.out.println("A, false");
+    Class.forName("A", false, cl3);
+
+    ClassLoader cl4 = create(boot, DEX1, DEX2);
+    System.out.println("C, true");
+    Class.forName("C", true, cl4);
+    System.out.println("A, true");
+    Class.forName("A", true, cl4);
+
+    ClassLoader cl5 = create(boot, DEX1, DEX2);
+    System.out.println("A, true");
+    Class.forName("A", true, cl5);
+    System.out.println("C, true");
+    Class.forName("C", true, cl5);
+
+    Thread t = new Thread(r, "TestRunner");
+    t.start();
+    t.join();
+
+    enableClassLoadEvents(false);
+  }
+
   private static void printClassLoaderClasses(ClassLoader cl) {
     for (;;) {
       if (cl == null || !cl.getClass().getName().startsWith("dalvik.system")) {
@@ -262,6 +333,12 @@
 
   private static native Class<?>[] getClassLoaderClasses(ClassLoader cl);
 
+  private static native int[] getClassVersion(Class<?> c);
+
+  private static native void enableClassLoadEvents(boolean b);
+
+  private static native void ensureJitCompiled(Class c, String name);
+
   private static class TestForNonInit {
     public static double dummy = Math.random();  // So it can't be compile-time initialized.
   }
diff --git a/test/913-heaps/src/Main.java b/test/913-heaps/src/Main.java
index 564596e..5a11a5b 100644
--- a/test/913-heaps/src/Main.java
+++ b/test/913-heaps/src/Main.java
@@ -21,8 +21,6 @@
 
 public class Main {
   public static void main(String[] args) throws Exception {
-    System.loadLibrary(args[1]);
-
     doTest();
     doFollowReferencesTest();
   }
diff --git a/test/914-hello-obsolescence/src/Main.java b/test/914-hello-obsolescence/src/Main.java
index 46266ef..8a14716 100644
--- a/test/914-hello-obsolescence/src/Main.java
+++ b/test/914-hello-obsolescence/src/Main.java
@@ -53,7 +53,6 @@
     "AAACIAAAEQAAAKIBAAADIAAAAgAAAJECAAAAIAAAAQAAAJ8CAAAAEAAAAQAAALACAAA=");
 
   public static void main(String[] args) {
-    System.loadLibrary(args[1]);
     doTest(new Transform());
   }
 
diff --git a/test/915-obsolete-2/src/Main.java b/test/915-obsolete-2/src/Main.java
index bbeb726..0e3145c 100644
--- a/test/915-obsolete-2/src/Main.java
+++ b/test/915-obsolete-2/src/Main.java
@@ -79,7 +79,6 @@
     "IAAAFwAAAD4CAAADIAAABAAAAAgEAAAAIAAAAQAAACYEAAAAEAAAAQAAADwEAAA=");
 
   public static void main(String[] args) {
-    System.loadLibrary(args[1]);
     doTest(new Transform());
   }
 
diff --git a/test/916-obsolete-jit/src/Main.java b/test/916-obsolete-jit/src/Main.java
index 1e43f7e..1b03200 100644
--- a/test/916-obsolete-jit/src/Main.java
+++ b/test/916-obsolete-jit/src/Main.java
@@ -113,7 +113,6 @@
   }
 
   public static void main(String[] args) {
-    System.loadLibrary(args[1]);
     doTest(new Transform(), new TestWatcher());
   }
 
diff --git a/test/917-fields-transformation/src/Main.java b/test/917-fields-transformation/src/Main.java
index 5378bb7..632a5c8 100644
--- a/test/917-fields-transformation/src/Main.java
+++ b/test/917-fields-transformation/src/Main.java
@@ -55,7 +55,6 @@
     "AAIgAAAMAAAAXAEAAAMgAAACAAAA4QEAAAAgAAABAAAA8AEAAAAQAAABAAAABAIAAA==");
 
   public static void main(String[] args) {
-    System.loadLibrary(args[1]);
     doTest(new Transform("Hello", "Goodbye"),
            new Transform("start", "end"));
   }
diff --git a/test/918-fields/src/Main.java b/test/918-fields/src/Main.java
index 8af6e7b..3ba535b 100644
--- a/test/918-fields/src/Main.java
+++ b/test/918-fields/src/Main.java
@@ -19,8 +19,6 @@
 
 public class Main {
   public static void main(String[] args) throws Exception {
-    System.loadLibrary(args[1]);
-
     doTest();
   }
 
diff --git a/test/919-obsolete-fields/src/Main.java b/test/919-obsolete-fields/src/Main.java
index 895c7a3..1d893f1 100644
--- a/test/919-obsolete-fields/src/Main.java
+++ b/test/919-obsolete-fields/src/Main.java
@@ -116,7 +116,6 @@
   }
 
   public static void main(String[] args) {
-    System.loadLibrary(args[1]);
     TestWatcher w = new TestWatcher();
     doTest(new Transform(w), w);
   }
diff --git a/test/921-hello-failure/expected.txt b/test/921-hello-failure/expected.txt
index 1c1d4d9..9615e6b 100644
--- a/test/921-hello-failure/expected.txt
+++ b/test/921-hello-failure/expected.txt
@@ -21,3 +21,11 @@
 Transformation error : java.lang.Exception(Failed to redefine classes <LTransform;, LTransform2;> due to JVMTI_ERROR_NAMES_DONT_MATCH)
 hello - MultiRedef
 hello2 - MultiRedef
+hello - MultiRetrans
+hello2 - MultiRetrans
+Transformation error : java.lang.Exception(Failed to retransform classes <LTransform2;, LTransform;> due to JVMTI_ERROR_NAMES_DONT_MATCH)
+hello - MultiRetrans
+hello2 - MultiRetrans
+Transformation error : java.lang.Exception(Failed to retransform classes <LTransform;, LTransform2;> due to JVMTI_ERROR_NAMES_DONT_MATCH)
+hello - MultiRetrans
+hello2 - MultiRetrans
diff --git a/test/921-hello-failure/src/Main.java b/test/921-hello-failure/src/Main.java
index 1fe2599..67ca1e1 100644
--- a/test/921-hello-failure/src/Main.java
+++ b/test/921-hello-failure/src/Main.java
@@ -18,13 +18,13 @@
 public class Main {
 
   public static void main(String[] args) {
-    System.loadLibrary(args[1]);
     NewName.doTest(new Transform());
     DifferentAccess.doTest(new Transform());
     NewInterface.doTest(new Transform2());
     MissingInterface.doTest(new Transform2());
     ReorderInterface.doTest(new Transform2());
     MultiRedef.doTest(new Transform(), new Transform2());
+    MultiRetrans.doTest(new Transform(), new Transform2());
   }
 
   // Transforms the class. This throws an exception if something goes wrong.
@@ -47,7 +47,20 @@
                                    dex_files.toArray(new byte[0][]));
   }
 
+  public static void addMultiTransformationResults(CommonClassDefinition... defs) throws Exception {
+    for (CommonClassDefinition d : defs) {
+      addCommonTransformationResult(d.target.getCanonicalName(),
+                                    d.class_file_bytes,
+                                    d.dex_file_bytes);
+    }
+  }
+
   public static native void doCommonMultiClassRedefinition(Class<?>[] targets,
                                                            byte[][] classfiles,
                                                            byte[][] dexfiles) throws Exception;
+  public static native void doCommonClassRetransformation(Class<?>... target) throws Exception;
+  public static native void enableCommonRetransformation(boolean enable);
+  public static native void addCommonTransformationResult(String target_name,
+                                                          byte[] class_bytes,
+                                                          byte[] dex_bytes);
 }
diff --git a/test/921-hello-failure/src/MultiRetrans.java b/test/921-hello-failure/src/MultiRetrans.java
new file mode 100644
index 0000000..95aaf07
--- /dev/null
+++ b/test/921-hello-failure/src/MultiRetrans.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.util.Base64;
+
+class MultiRetrans {
+
+  // class NotTransform {
+  //   public void sayHi(String name) {
+  //     throw new Error("Should not be called!");
+  //   }
+  // }
+  private static CommonClassDefinition INVALID_DEFINITION_T1 = new CommonClassDefinition(
+      Transform.class,
+      Base64.getDecoder().decode(
+          "yv66vgAAADQAFQoABgAPBwAQCAARCgACABIHABMHABQBAAY8aW5pdD4BAAMoKVYBAARDb2RlAQAP" +
+          "TGluZU51bWJlclRhYmxlAQAFc2F5SGkBABUoTGphdmEvbGFuZy9TdHJpbmc7KVYBAApTb3VyY2VG" +
+          "aWxlAQARTm90VHJhbnNmb3JtLmphdmEMAAcACAEAD2phdmEvbGFuZy9FcnJvcgEAFVNob3VsZCBu" +
+          "b3QgYmUgY2FsbGVkIQwABwAMAQAMTm90VHJhbnNmb3JtAQAQamF2YS9sYW5nL09iamVjdAAgAAUA" +
+          "BgAAAAAAAgAAAAcACAABAAkAAAAdAAEAAQAAAAUqtwABsQAAAAEACgAAAAYAAQAAAAEAAQALAAwA" +
+          "AQAJAAAAIgADAAIAAAAKuwACWRIDtwAEvwAAAAEACgAAAAYAAQAAAAMAAQANAAAAAgAO"),
+      Base64.getDecoder().decode(
+          "ZGV4CjAzNQDLV95i5xnv6iUi6uIeDoY5jP5Xe9NP1AiYAgAAcAAAAHhWNBIAAAAAAAAAAAQCAAAL" +
+          "AAAAcAAAAAUAAACcAAAAAgAAALAAAAAAAAAAAAAAAAQAAADIAAAAAQAAAOgAAACQAQAACAEAAEoB" +
+          "AABSAQAAYgEAAHUBAACJAQAAnQEAALABAADHAQAAygEAAM4BAADiAQAAAQAAAAIAAAADAAAABAAA" +
+          "AAcAAAAHAAAABAAAAAAAAAAIAAAABAAAAEQBAAAAAAAAAAAAAAAAAQAKAAAAAQABAAAAAAACAAAA" +
+          "AAAAAAAAAAAAAAAAAgAAAAAAAAAFAAAAAAAAAPQBAAAAAAAAAQABAAEAAADpAQAABAAAAHAQAwAA" +
+          "AA4ABAACAAIAAADuAQAACQAAACIAAQAbAQYAAABwIAIAEAAnAAAAAQAAAAMABjxpbml0PgAOTE5v" +
+          "dFRyYW5zZm9ybTsAEUxqYXZhL2xhbmcvRXJyb3I7ABJMamF2YS9sYW5nL09iamVjdDsAEkxqYXZh" +
+          "L2xhbmcvU3RyaW5nOwARTm90VHJhbnNmb3JtLmphdmEAFVNob3VsZCBub3QgYmUgY2FsbGVkIQAB" +
+          "VgACVkwAEmVtaXR0ZXI6IGphY2stNC4yMAAFc2F5SGkAAQAHDgADAQAHDgAAAAEBAICABIgCAQGg" +
+          "AgAADAAAAAAAAAABAAAAAAAAAAEAAAALAAAAcAAAAAIAAAAFAAAAnAAAAAMAAAACAAAAsAAAAAUA" +
+          "AAAEAAAAyAAAAAYAAAABAAAA6AAAAAEgAAACAAAACAEAAAEQAAABAAAARAEAAAIgAAALAAAASgEA" +
+          "AAMgAAACAAAA6QEAAAAgAAABAAAA9AEAAAAQAAABAAAABAIAAA=="));
+
+  // Valid redefinition of Transform2
+  // class Transform2 implements Iface1, Iface2 {
+  //   public void sayHi(String name) {
+  //     throw new Error("Should not be called!");
+  //   }
+  // }
+  private static CommonClassDefinition VALID_DEFINITION_T2 = new CommonClassDefinition(
+      Transform2.class,
+      Base64.getDecoder().decode(
+          "yv66vgAAADQAGQoABgARBwASCAATCgACABQHABUHABYHABcHABgBAAY8aW5pdD4BAAMoKVYBAARD" +
+          "b2RlAQAPTGluZU51bWJlclRhYmxlAQAFc2F5SGkBABUoTGphdmEvbGFuZy9TdHJpbmc7KVYBAApT" +
+          "b3VyY2VGaWxlAQAPVHJhbnNmb3JtMi5qYXZhDAAJAAoBAA9qYXZhL2xhbmcvRXJyb3IBABVTaG91" +
+          "bGQgbm90IGJlIGNhbGxlZCEMAAkADgEAClRyYW5zZm9ybTIBABBqYXZhL2xhbmcvT2JqZWN0AQAG" +
+          "SWZhY2UxAQAGSWZhY2UyACAABQAGAAIABwAIAAAAAgAAAAkACgABAAsAAAAdAAEAAQAAAAUqtwAB" +
+          "sQAAAAEADAAAAAYAAQAAAAEAAQANAA4AAQALAAAAIgADAAIAAAAKuwACWRIDtwAEvwAAAAEADAAA" +
+          "AAYAAQAAAAMAAQAPAAAAAgAQ"),
+      Base64.getDecoder().decode(
+          "ZGV4CjAzNQDSWls05CPkX+gbTGMVRvx9dc9vozzVbu7AAgAAcAAAAHhWNBIAAAAAAAAAACwCAAAN" +
+          "AAAAcAAAAAcAAACkAAAAAgAAAMAAAAAAAAAAAAAAAAQAAADYAAAAAQAAAPgAAACoAQAAGAEAAGIB" +
+          "AABqAQAAdAEAAH4BAACMAQAAnwEAALMBAADHAQAA3gEAAO8BAADyAQAA9gEAAAoCAAABAAAAAgAA" +
+          "AAMAAAAEAAAABQAAAAYAAAAJAAAACQAAAAYAAAAAAAAACgAAAAYAAABcAQAAAgAAAAAAAAACAAEA" +
+          "DAAAAAMAAQAAAAAABAAAAAAAAAACAAAAAAAAAAQAAABUAQAACAAAAAAAAAAcAgAAAAAAAAEAAQAB" +
+          "AAAAEQIAAAQAAABwEAMAAAAOAAQAAgACAAAAFgIAAAkAAAAiAAMAGwEHAAAAcCACABAAJwAAAAIA" +
+          "AAAAAAEAAQAAAAUABjxpbml0PgAITElmYWNlMTsACExJZmFjZTI7AAxMVHJhbnNmb3JtMjsAEUxq" +
+          "YXZhL2xhbmcvRXJyb3I7ABJMamF2YS9sYW5nL09iamVjdDsAEkxqYXZhL2xhbmcvU3RyaW5nOwAV" +
+          "U2hvdWxkIG5vdCBiZSBjYWxsZWQhAA9UcmFuc2Zvcm0yLmphdmEAAVYAAlZMABJlbWl0dGVyOiBq" +
+          "YWNrLTQuMjAABXNheUhpAAEABw4AAwEABw4AAAABAQCAgASYAgEBsAIAAAwAAAAAAAAAAQAAAAAA" +
+          "AAABAAAADQAAAHAAAAACAAAABwAAAKQAAAADAAAAAgAAAMAAAAAFAAAABAAAANgAAAAGAAAAAQAA" +
+          "APgAAAABIAAAAgAAABgBAAABEAAAAgAAAFQBAAACIAAADQAAAGIBAAADIAAAAgAAABECAAAAIAAA" +
+          "AQAAABwCAAAAEAAAAQAAACwCAAA="));
+
+  public static void doTest(Transform t1, Transform2 t2) {
+    t1.sayHi("MultiRetrans");
+    t2.sayHi("MultiRetrans");
+    try {
+      Main.addMultiTransformationResults(VALID_DEFINITION_T2, INVALID_DEFINITION_T1);
+      Main.enableCommonRetransformation(true);
+      Main.doCommonClassRetransformation(Transform2.class, Transform.class);
+    } catch (Exception e) {
+      System.out.println(
+          "Transformation error : " + e.getClass().getName() + "(" + e.getMessage() + ")");
+    } finally {
+      Main.enableCommonRetransformation(false);
+    }
+    t1.sayHi("MultiRetrans");
+    t2.sayHi("MultiRetrans");
+    try {
+      Main.addMultiTransformationResults(VALID_DEFINITION_T2, INVALID_DEFINITION_T1);
+      Main.enableCommonRetransformation(true);
+      Main.doCommonClassRetransformation(Transform.class, Transform2.class);
+    } catch (Exception e) {
+      System.out.println(
+          "Transformation error : " + e.getClass().getName() + "(" + e.getMessage() + ")");
+    } finally {
+      Main.enableCommonRetransformation(false);
+    }
+    t1.sayHi("MultiRetrans");
+    t2.sayHi("MultiRetrans");
+  }
+}
diff --git a/test/922-properties/src/Main.java b/test/922-properties/src/Main.java
index 6cec6e9..8ad742f 100644
--- a/test/922-properties/src/Main.java
+++ b/test/922-properties/src/Main.java
@@ -19,8 +19,6 @@
 
 public class Main {
   public static void main(String[] args) throws Exception {
-    System.loadLibrary(args[1]);
-
     doTest();
   }
 
diff --git a/test/923-monitors/src/Main.java b/test/923-monitors/src/Main.java
index e35ce12..ef00728 100644
--- a/test/923-monitors/src/Main.java
+++ b/test/923-monitors/src/Main.java
@@ -21,8 +21,6 @@
 
 public class Main {
   public static void main(String[] args) throws Exception {
-    System.loadLibrary(args[1]);
-
     doTest();
   }
 
diff --git a/test/924-threads/expected.txt b/test/924-threads/expected.txt
index 32e3368..67d20eb 100644
--- a/test/924-threads/expected.txt
+++ b/test/924-threads/expected.txt
@@ -29,3 +29,9 @@
 5 = ALIVE|RUNNABLE
 2 = TERMINATED
 [Thread[FinalizerDaemon,5,system], Thread[FinalizerWatchdogDaemon,5,system], Thread[HeapTaskDaemon,5,system], Thread[ReferenceQueueDaemon,5,system], Thread[Signal Catcher,5,system], Thread[main,5,main]]
+JVMTI_ERROR_THREAD_NOT_ALIVE
+JVMTI_ERROR_THREAD_NOT_ALIVE
+Constructed thread
+Thread(EventTestThread): start
+Thread(EventTestThread): end
+Thread joined
diff --git a/test/924-threads/src/Main.java b/test/924-threads/src/Main.java
index 492a7ac..29c4aa3 100644
--- a/test/924-threads/src/Main.java
+++ b/test/924-threads/src/Main.java
@@ -25,8 +25,6 @@
 
 public class Main {
   public static void main(String[] args) throws Exception {
-    System.loadLibrary(args[1]);
-
     doTest();
   }
 
@@ -56,6 +54,10 @@
     doStateTests();
 
     doAllThreadsTests();
+
+    doTLSTests();
+
+    doTestEvents();
   }
 
   private static class Holder {
@@ -164,6 +166,84 @@
     System.out.println(Arrays.toString(threads));
   }
 
+  private static void doTLSTests() throws Exception {
+    doTLSNonLiveTests();
+    doTLSLiveTests();
+  }
+
+  private static void doTLSNonLiveTests() throws Exception {
+    Thread t = new Thread();
+    try {
+      setTLS(t, 1);
+      System.out.println("Expected failure setting TLS for non-live thread");
+    } catch (Exception e) {
+      System.out.println(e.getMessage());
+    }
+    t.start();
+    t.join();
+    try {
+      setTLS(t, 1);
+      System.out.println("Expected failure setting TLS for non-live thread");
+    } catch (Exception e) {
+      System.out.println(e.getMessage());
+    }
+  }
+
+  private static void doTLSLiveTests() throws Exception {
+    setTLS(Thread.currentThread(), 1);
+
+    long l = getTLS(Thread.currentThread());
+    if (l != 1) {
+      throw new RuntimeException("Unexpected TLS value: " + l);
+    };
+
+    final CountDownLatch cdl1 = new CountDownLatch(1);
+    final CountDownLatch cdl2 = new CountDownLatch(1);
+
+    Runnable r = new Runnable() {
+      @Override
+      public void run() {
+        try {
+          cdl1.countDown();
+          cdl2.await();
+          setTLS(Thread.currentThread(), 2);
+          if (getTLS(Thread.currentThread()) != 2) {
+            throw new RuntimeException("Different thread issue");
+          }
+        } catch (Exception e) {
+          throw new RuntimeException(e);
+        }
+      }
+    };
+
+    Thread t = new Thread(r);
+    t.start();
+    cdl1.await();
+    setTLS(Thread.currentThread(), 1);
+    cdl2.countDown();
+
+    t.join();
+    if (getTLS(Thread.currentThread()) != 1) {
+      throw new RuntimeException("Got clobbered");
+    }
+  }
+
+  private static void doTestEvents() throws Exception {
+    enableThreadEvents(true);
+
+    Thread t = new Thread("EventTestThread");
+
+    System.out.println("Constructed thread");
+    Thread.yield();
+
+    t.start();
+    t.join();
+
+    System.out.println("Thread joined");
+
+    enableThreadEvents(false);
+  }
+
   private final static Comparator<Thread> THREAD_COMP = new Comparator<Thread>() {
     public int compare(Thread o1, Thread o2) {
       return o1.getName().compareTo(o2.getName());
@@ -229,4 +309,7 @@
   private static native Object[] getThreadInfo(Thread t);
   private static native int getThreadState(Thread t);
   private static native Thread[] getAllThreads();
+  private static native void setTLS(Thread t, long l);
+  private static native long getTLS(Thread t);
+  private static native void enableThreadEvents(boolean b);
 }
diff --git a/test/924-threads/threads.cc b/test/924-threads/threads.cc
index 1487b7c..0380433 100644
--- a/test/924-threads/threads.cc
+++ b/test/924-threads/threads.cc
@@ -120,5 +120,88 @@
   return ret;
 }
 
+extern "C" JNIEXPORT jlong JNICALL Java_Main_getTLS(
+    JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED, jthread thread) {
+  void* tls;
+  jvmtiError result = jvmti_env->GetThreadLocalStorage(thread, &tls);
+  if (JvmtiErrorToException(env, result)) {
+    return 0;
+  }
+  return static_cast<jlong>(reinterpret_cast<uintptr_t>(tls));
+}
+
+extern "C" JNIEXPORT void JNICALL Java_Main_setTLS(
+    JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED, jthread thread, jlong val) {
+  const void* tls = reinterpret_cast<void*>(static_cast<uintptr_t>(val));
+  jvmtiError result = jvmti_env->SetThreadLocalStorage(thread, tls);
+  JvmtiErrorToException(env, result);
+}
+
+static void JNICALL ThreadEvent(jvmtiEnv* jvmti_env,
+                                JNIEnv* jni_env,
+                                jthread thread,
+                                bool is_start) {
+  jvmtiThreadInfo info;
+  jvmtiError result = jvmti_env->GetThreadInfo(thread, &info);
+  if (result != JVMTI_ERROR_NONE) {
+    printf("Error getting thread info");
+    return;
+  }
+  printf("Thread(%s): %s\n", info.name, is_start ? "start" : "end");
+
+  jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(info.name));
+  jni_env->DeleteLocalRef(info.thread_group);
+  jni_env->DeleteLocalRef(info.context_class_loader);
+}
+
+static void JNICALL ThreadStart(jvmtiEnv* jvmti_env,
+                                JNIEnv* jni_env,
+                                jthread thread) {
+  ThreadEvent(jvmti_env, jni_env, thread, true);
+}
+
+static void JNICALL ThreadEnd(jvmtiEnv* jvmti_env,
+                              JNIEnv* jni_env,
+                              jthread thread) {
+  ThreadEvent(jvmti_env, jni_env, thread, false);
+}
+
+extern "C" JNIEXPORT void JNICALL Java_Main_enableThreadEvents(
+    JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED, jboolean b) {
+  if (b == JNI_FALSE) {
+    jvmtiError ret = jvmti_env->SetEventNotificationMode(JVMTI_DISABLE,
+                                                         JVMTI_EVENT_THREAD_START,
+                                                         nullptr);
+    if (JvmtiErrorToException(env, ret)) {
+      return;
+    }
+    ret = jvmti_env->SetEventNotificationMode(JVMTI_DISABLE,
+                                              JVMTI_EVENT_THREAD_END,
+                                              nullptr);
+    JvmtiErrorToException(env, ret);
+    return;
+  }
+
+  jvmtiEventCallbacks callbacks;
+  memset(&callbacks, 0, sizeof(jvmtiEventCallbacks));
+  callbacks.ThreadStart = ThreadStart;
+  callbacks.ThreadEnd = ThreadEnd;
+  jvmtiError ret = jvmti_env->SetEventCallbacks(&callbacks, sizeof(callbacks));
+  if (JvmtiErrorToException(env, ret)) {
+    return;
+  }
+
+  ret = jvmti_env->SetEventNotificationMode(JVMTI_ENABLE,
+                                            JVMTI_EVENT_THREAD_START,
+                                            nullptr);
+  if (JvmtiErrorToException(env, ret)) {
+    return;
+  }
+  ret = jvmti_env->SetEventNotificationMode(JVMTI_ENABLE,
+                                            JVMTI_EVENT_THREAD_END,
+                                            nullptr);
+  JvmtiErrorToException(env, ret);
+}
+
 }  // namespace Test924Threads
 }  // namespace art
diff --git a/test/925-threadgroups/src/Main.java b/test/925-threadgroups/src/Main.java
index c59efe2..3d7a4ca 100644
--- a/test/925-threadgroups/src/Main.java
+++ b/test/925-threadgroups/src/Main.java
@@ -19,8 +19,6 @@
 
 public class Main {
   public static void main(String[] args) throws Exception {
-    System.loadLibrary(args[1]);
-
     doTest();
   }
 
diff --git a/test/926-multi-obsolescence/src/Main.java b/test/926-multi-obsolescence/src/Main.java
index 8a6cf84..6d9f96c 100644
--- a/test/926-multi-obsolescence/src/Main.java
+++ b/test/926-multi-obsolescence/src/Main.java
@@ -92,7 +92,6 @@
         "AACUAQAAAiAAABEAAACiAQAAAyAAAAIAAACXAgAAACAAAAEAAAClAgAAABAAAAEAAAC0AgAA"));
 
   public static void main(String[] args) {
-    System.loadLibrary(args[1]);
     doTest(new Transform(), new Transform2());
   }
 
diff --git a/test/927-timers/src/Main.java b/test/927-timers/src/Main.java
index 2f5c85c..b67f66d 100644
--- a/test/927-timers/src/Main.java
+++ b/test/927-timers/src/Main.java
@@ -18,8 +18,6 @@
 
 public class Main {
   public static void main(String[] args) throws Exception {
-    System.loadLibrary(args[1]);
-
     doTest();
   }
 
diff --git a/test/928-jni-table/src/Main.java b/test/928-jni-table/src/Main.java
index b0baea1..fd61b7d 100644
--- a/test/928-jni-table/src/Main.java
+++ b/test/928-jni-table/src/Main.java
@@ -16,8 +16,6 @@
 
 public class Main {
   public static void main(String[] args) throws Exception {
-    System.loadLibrary(args[1]);
-
     doJNITableTest();
 
     System.out.println("Done");
diff --git a/test/929-search/src/Main.java b/test/929-search/src/Main.java
index d253e6f..bbeb081 100644
--- a/test/929-search/src/Main.java
+++ b/test/929-search/src/Main.java
@@ -18,8 +18,6 @@
 
 public class Main {
   public static void main(String[] args) throws Exception {
-    System.loadLibrary(args[1]);
-
     doTest();
   }
 
diff --git a/test/930-hello-retransform/build b/test/930-hello-retransform/build
new file mode 100755
index 0000000..898e2e5
--- /dev/null
+++ b/test/930-hello-retransform/build
@@ -0,0 +1,17 @@
+#!/bin/bash
+#
+# Copyright 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+./default-build "$@" --experimental agents
diff --git a/test/930-hello-retransform/expected.txt b/test/930-hello-retransform/expected.txt
new file mode 100644
index 0000000..4774b81
--- /dev/null
+++ b/test/930-hello-retransform/expected.txt
@@ -0,0 +1,2 @@
+hello
+Goodbye
diff --git a/test/930-hello-retransform/info.txt b/test/930-hello-retransform/info.txt
new file mode 100644
index 0000000..875a5f6
--- /dev/null
+++ b/test/930-hello-retransform/info.txt
@@ -0,0 +1 @@
+Tests basic functions in the jvmti plugin.
diff --git a/test/930-hello-retransform/run b/test/930-hello-retransform/run
new file mode 100755
index 0000000..4379349
--- /dev/null
+++ b/test/930-hello-retransform/run
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+./default-run "$@" --experimental agents \
+                   --experimental runtime-plugins \
+                   --jvmti
diff --git a/test/930-hello-retransform/src/Main.java b/test/930-hello-retransform/src/Main.java
new file mode 100644
index 0000000..0063c82
--- /dev/null
+++ b/test/930-hello-retransform/src/Main.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.util.Base64;
+public class Main {
+
+  /**
+   * base64 encoded class/dex file for
+   * class Transform {
+   *   public void sayHi() {
+   *    System.out.println("Goodbye");
+   *   }
+   * }
+   */
+  private static final byte[] CLASS_BYTES = Base64.getDecoder().decode(
+    "yv66vgAAADQAHAoABgAOCQAPABAIABEKABIAEwcAFAcAFQEABjxpbml0PgEAAygpVgEABENvZGUB" +
+    "AA9MaW5lTnVtYmVyVGFibGUBAAVzYXlIaQEAClNvdXJjZUZpbGUBAA5UcmFuc2Zvcm0uamF2YQwA" +
+    "BwAIBwAWDAAXABgBAAdHb29kYnllBwAZDAAaABsBAAlUcmFuc2Zvcm0BABBqYXZhL2xhbmcvT2Jq" +
+    "ZWN0AQAQamF2YS9sYW5nL1N5c3RlbQEAA291dAEAFUxqYXZhL2lvL1ByaW50U3RyZWFtOwEAE2ph" +
+    "dmEvaW8vUHJpbnRTdHJlYW0BAAdwcmludGxuAQAVKExqYXZhL2xhbmcvU3RyaW5nOylWACAABQAG" +
+    "AAAAAAACAAAABwAIAAEACQAAAB0AAQABAAAABSq3AAGxAAAAAQAKAAAABgABAAAAEQABAAsACAAB" +
+    "AAkAAAAlAAIAAQAAAAmyAAISA7YABLEAAAABAAoAAAAKAAIAAAATAAgAFAABAAwAAAACAA0=");
+  private static final byte[] DEX_BYTES = Base64.getDecoder().decode(
+    "ZGV4CjAzNQCLXSBQ5FiS3f16krSYZFF8xYZtFVp0GRXMAgAAcAAAAHhWNBIAAAAAAAAAACwCAAAO" +
+    "AAAAcAAAAAYAAACoAAAAAgAAAMAAAAABAAAA2AAAAAQAAADgAAAAAQAAAAABAACsAQAAIAEAAGIB" +
+    "AABqAQAAcwEAAIABAACXAQAAqwEAAL8BAADTAQAA4wEAAOYBAADqAQAA/gEAAAMCAAAMAgAAAgAA" +
+    "AAMAAAAEAAAABQAAAAYAAAAIAAAACAAAAAUAAAAAAAAACQAAAAUAAABcAQAABAABAAsAAAAAAAAA" +
+    "AAAAAAAAAAANAAAAAQABAAwAAAACAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAHAAAAAAAAAB4CAAAA" +
+    "AAAAAQABAAEAAAATAgAABAAAAHAQAwAAAA4AAwABAAIAAAAYAgAACQAAAGIAAAAbAQEAAABuIAIA" +
+    "EAAOAAAAAQAAAAMABjxpbml0PgAHR29vZGJ5ZQALTFRyYW5zZm9ybTsAFUxqYXZhL2lvL1ByaW50" +
+    "U3RyZWFtOwASTGphdmEvbGFuZy9PYmplY3Q7ABJMamF2YS9sYW5nL1N0cmluZzsAEkxqYXZhL2xh" +
+    "bmcvU3lzdGVtOwAOVHJhbnNmb3JtLmphdmEAAVYAAlZMABJlbWl0dGVyOiBqYWNrLTMuMzYAA291" +
+    "dAAHcHJpbnRsbgAFc2F5SGkAEQAHDgATAAcOhQAAAAEBAICABKACAQG4Ag0AAAAAAAAAAQAAAAAA" +
+    "AAABAAAADgAAAHAAAAACAAAABgAAAKgAAAADAAAAAgAAAMAAAAAEAAAAAQAAANgAAAAFAAAABAAA" +
+    "AOAAAAAGAAAAAQAAAAABAAABIAAAAgAAACABAAABEAAAAQAAAFwBAAACIAAADgAAAGIBAAADIAAA" +
+    "AgAAABMCAAAAIAAAAQAAAB4CAAAAEAAAAQAAACwCAAA=");
+
+  public static void main(String[] args) {
+    doTest(new Transform());
+  }
+
+  public static void doTest(Transform t) {
+    t.sayHi();
+    addCommonTransformationResult("Transform", CLASS_BYTES, DEX_BYTES);
+    enableCommonRetransformation(true);
+    doCommonClassRetransformation(Transform.class);
+    t.sayHi();
+  }
+
+  // Transforms the class
+  private static native void doCommonClassRetransformation(Class<?>... target);
+  private static native void enableCommonRetransformation(boolean enable);
+  private static native void addCommonTransformationResult(String target_name,
+                                                           byte[] class_bytes,
+                                                           byte[] dex_bytes);
+}
diff --git a/test/930-hello-retransform/src/Transform.java b/test/930-hello-retransform/src/Transform.java
new file mode 100644
index 0000000..8e8af35
--- /dev/null
+++ b/test/930-hello-retransform/src/Transform.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+class Transform {
+  public void sayHi() {
+    // Use lower 'h' to make sure the string will have a different string id
+    // than the transformation (the transformation code is the same except
+    // the actual printed String, which was making the test inacurately passing
+    // in JIT mode when loading the string from the dex cache, as the string ids
+    // of the two different strings were the same).
+    // We know the string ids will be different because lexicographically:
+    // "Goodbye" < "LTransform;" < "hello".
+    System.out.println("hello");
+  }
+}
diff --git a/test/931-agent-thread/agent_thread.cc b/test/931-agent-thread/agent_thread.cc
new file mode 100644
index 0000000..6ace4ce
--- /dev/null
+++ b/test/931-agent-thread/agent_thread.cc
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <inttypes.h>
+
+#include "barrier.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "jni.h"
+#include "openjdkjvmti/jvmti.h"
+#include "runtime.h"
+#include "ScopedLocalRef.h"
+#include "thread-inl.h"
+#include "well_known_classes.h"
+
+#include "ti-agent/common_helper.h"
+#include "ti-agent/common_load.h"
+
+namespace art {
+namespace Test930AgentThread {
+
+struct AgentData {
+  AgentData() : main_thread(nullptr),
+                jvmti_env(nullptr),
+                b(2) {
+  }
+
+  jthread main_thread;
+  jvmtiEnv* jvmti_env;
+  Barrier b;
+  jint priority;
+};
+
+static void AgentMain(jvmtiEnv* jenv, JNIEnv* env, void* arg) {
+  AgentData* data = reinterpret_cast<AgentData*>(arg);
+
+  // Check some basics.
+  // This thread is not the main thread.
+  jthread this_thread;
+  jvmtiError this_thread_result = jenv->GetCurrentThread(&this_thread);
+  CHECK(!JvmtiErrorToException(env, this_thread_result));
+  CHECK(!env->IsSameObject(this_thread, data->main_thread));
+
+  // The thread is a daemon.
+  jvmtiThreadInfo info;
+  jvmtiError info_result = jenv->GetThreadInfo(this_thread, &info);
+  CHECK(!JvmtiErrorToException(env, info_result));
+  CHECK(info.is_daemon);
+
+  // The thread has the requested priority.
+  // TODO: Our thread priorities do not work on the host.
+  // CHECK_EQ(info.priority, data->priority);
+
+  // Check further parts of the thread:
+  jint thread_count;
+  jthread* threads;
+  jvmtiError threads_result = jenv->GetAllThreads(&thread_count, &threads);
+  CHECK(!JvmtiErrorToException(env, threads_result));
+  bool found = false;
+  for (jint i = 0; i != thread_count; ++i) {
+    if (env->IsSameObject(threads[i], this_thread)) {
+      found = true;
+      break;
+    }
+  }
+  CHECK(found);
+
+  // Done, let the main thread progress.
+  data->b.Pass(Thread::Current());
+}
+
+extern "C" JNIEXPORT void JNICALL Java_Main_testAgentThread(
+    JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED) {
+  // Create a Thread object.
+  ScopedLocalRef<jobject> thread_name(env,
+                                      env->NewStringUTF("Agent Thread"));
+  if (thread_name.get() == nullptr) {
+    return;
+  }
+
+  ScopedLocalRef<jobject> thread(env, env->AllocObject(WellKnownClasses::java_lang_Thread));
+  if (thread.get() == nullptr) {
+    return;
+  }
+
+  env->CallNonvirtualVoidMethod(thread.get(),
+                                WellKnownClasses::java_lang_Thread,
+                                WellKnownClasses::java_lang_Thread_init,
+                                Runtime::Current()->GetMainThreadGroup(),
+                                thread_name.get(),
+                                kMinThreadPriority,
+                                JNI_FALSE);
+  if (env->ExceptionCheck()) {
+    return;
+  }
+
+  jthread main_thread;
+  jvmtiError main_thread_result = jvmti_env->GetCurrentThread(&main_thread);
+  if (JvmtiErrorToException(env, main_thread_result)) {
+    return;
+  }
+
+  AgentData data;
+  data.main_thread = env->NewGlobalRef(main_thread);
+  data.jvmti_env = jvmti_env;
+  data.priority = JVMTI_THREAD_MIN_PRIORITY;
+
+  jvmtiError result = jvmti_env->RunAgentThread(thread.get(), AgentMain, &data, data.priority);
+  if (JvmtiErrorToException(env, result)) {
+    return;
+  }
+
+  data.b.Wait(Thread::Current());
+
+  env->DeleteGlobalRef(data.main_thread);
+}
+
+}  // namespace Test930AgentThread
+}  // namespace art
diff --git a/test/931-agent-thread/build b/test/931-agent-thread/build
new file mode 100755
index 0000000..898e2e5
--- /dev/null
+++ b/test/931-agent-thread/build
@@ -0,0 +1,17 @@
+#!/bin/bash
+#
+# Copyright 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+./default-build "$@" --experimental agents
diff --git a/test/931-agent-thread/expected.txt b/test/931-agent-thread/expected.txt
new file mode 100644
index 0000000..a965a70
--- /dev/null
+++ b/test/931-agent-thread/expected.txt
@@ -0,0 +1 @@
+Done
diff --git a/test/931-agent-thread/info.txt b/test/931-agent-thread/info.txt
new file mode 100644
index 0000000..875a5f6
--- /dev/null
+++ b/test/931-agent-thread/info.txt
@@ -0,0 +1 @@
+Tests basic functions in the jvmti plugin.
diff --git a/test/931-agent-thread/run b/test/931-agent-thread/run
new file mode 100755
index 0000000..0a8d067
--- /dev/null
+++ b/test/931-agent-thread/run
@@ -0,0 +1,23 @@
+#!/bin/bash
+#
+# Copyright 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# This test checks whether dex files can be injected into parent classloaders. App images preload
+# classes, which will make the injection moot. Turn off app images to avoid the issue.
+
+./default-run "$@" --experimental agents \
+                   --experimental runtime-plugins \
+                   --jvmti \
+                   --no-app-image
diff --git a/test/931-agent-thread/src/Main.java b/test/931-agent-thread/src/Main.java
new file mode 100644
index 0000000..a7639fb
--- /dev/null
+++ b/test/931-agent-thread/src/Main.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.util.Arrays;
+
+public class Main {
+  public static void main(String[] args) throws Exception {
+    testAgentThread();
+
+    System.out.println("Done");
+  }
+
+  private static native void testAgentThread();
+}
diff --git a/test/932-transform-saves/build b/test/932-transform-saves/build
new file mode 100755
index 0000000..898e2e5
--- /dev/null
+++ b/test/932-transform-saves/build
@@ -0,0 +1,17 @@
+#!/bin/bash
+#
+# Copyright 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+./default-build "$@" --experimental agents
diff --git a/test/932-transform-saves/expected.txt b/test/932-transform-saves/expected.txt
new file mode 100644
index 0000000..5097771
--- /dev/null
+++ b/test/932-transform-saves/expected.txt
@@ -0,0 +1,3 @@
+hello
+Goodbye
+hello
diff --git a/test/932-transform-saves/info.txt b/test/932-transform-saves/info.txt
new file mode 100644
index 0000000..875a5f6
--- /dev/null
+++ b/test/932-transform-saves/info.txt
@@ -0,0 +1 @@
+Tests basic functions in the jvmti plugin.
diff --git a/test/932-transform-saves/run b/test/932-transform-saves/run
new file mode 100755
index 0000000..4379349
--- /dev/null
+++ b/test/932-transform-saves/run
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+./default-run "$@" --experimental agents \
+                   --experimental runtime-plugins \
+                   --jvmti
diff --git a/test/932-transform-saves/src/Main.java b/test/932-transform-saves/src/Main.java
new file mode 100644
index 0000000..d960322
--- /dev/null
+++ b/test/932-transform-saves/src/Main.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.util.Base64;
+public class Main {
+  /**
+   * base64 encoded class/dex file for
+   * class Transform {
+   *   public void sayHi() {
+   *    System.out.println("hello");
+   *   }
+   * }
+   */
+  private static final byte[] CLASS_BYTES_A = Base64.getDecoder().decode(
+      "yv66vgAAADQAHAoABgAOCQAPABAIABEKABIAEwcAFAcAFQEABjxpbml0PgEAAygpVgEABENvZGUB" +
+      "AA9MaW5lTnVtYmVyVGFibGUBAAVzYXlIaQEAClNvdXJjZUZpbGUBAA5UcmFuc2Zvcm0uamF2YQwA" +
+      "BwAIBwAWDAAXABgBAAVoZWxsbwcAGQwAGgAbAQAJVHJhbnNmb3JtAQAQamF2YS9sYW5nL09iamVj" +
+      "dAEAEGphdmEvbGFuZy9TeXN0ZW0BAANvdXQBABVMamF2YS9pby9QcmludFN0cmVhbTsBABNqYXZh" +
+      "L2lvL1ByaW50U3RyZWFtAQAHcHJpbnRsbgEAFShMamF2YS9sYW5nL1N0cmluZzspVgAgAAUABgAA" +
+      "AAAAAgAAAAcACAABAAkAAAAdAAEAAQAAAAUqtwABsQAAAAEACgAAAAYAAQAAABEAAQALAAgAAQAJ" +
+      "AAAAJQACAAEAAAAJsgACEgO2AASxAAAAAQAKAAAACgACAAAAGgAIABsAAQAMAAAAAgAN");
+  private static final byte[] DEX_BYTES_A = Base64.getDecoder().decode(
+      "ZGV4CjAzNQC6XWInnnDd1H4NdQ3P3inH8eCVmQI6W7LMAgAAcAAAAHhWNBIAAAAAAAAAACwCAAAO" +
+      "AAAAcAAAAAYAAACoAAAAAgAAAMAAAAABAAAA2AAAAAQAAADgAAAAAQAAAAABAACsAQAAIAEAAGIB" +
+      "AABqAQAAdwEAAI4BAACiAQAAtgEAAMoBAADaAQAA3QEAAOEBAAD1AQAA/AEAAAECAAAKAgAAAQAA" +
+      "AAIAAAADAAAABAAAAAUAAAAHAAAABwAAAAUAAAAAAAAACAAAAAUAAABcAQAABAABAAsAAAAAAAAA" +
+      "AAAAAAAAAAANAAAAAQABAAwAAAACAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAGAAAAAAAAABwCAAAA" +
+      "AAAAAQABAAEAAAARAgAABAAAAHAQAwAAAA4AAwABAAIAAAAWAgAACQAAAGIAAAAbAQoAAABuIAIA" +
+      "EAAOAAAAAQAAAAMABjxpbml0PgALTFRyYW5zZm9ybTsAFUxqYXZhL2lvL1ByaW50U3RyZWFtOwAS" +
+      "TGphdmEvbGFuZy9PYmplY3Q7ABJMamF2YS9sYW5nL1N0cmluZzsAEkxqYXZhL2xhbmcvU3lzdGVt" +
+      "OwAOVHJhbnNmb3JtLmphdmEAAVYAAlZMABJlbWl0dGVyOiBqYWNrLTQuMjIABWhlbGxvAANvdXQA" +
+      "B3ByaW50bG4ABXNheUhpABEABw4AGgAHDocAAAABAQCAgASgAgEBuAIAAA0AAAAAAAAAAQAAAAAA" +
+      "AAABAAAADgAAAHAAAAACAAAABgAAAKgAAAADAAAAAgAAAMAAAAAEAAAAAQAAANgAAAAFAAAABAAA" +
+      "AOAAAAAGAAAAAQAAAAABAAABIAAAAgAAACABAAABEAAAAQAAAFwBAAACIAAADgAAAGIBAAADIAAA" +
+      "AgAAABECAAAAIAAAAQAAABwCAAAAEAAAAQAAACwCAAA=");
+
+  /**
+   * base64 encoded class/dex file for
+   * class Transform {
+   *   public void sayHi() {
+   *    System.out.println("Goodbye");
+   *   }
+   * }
+   */
+  private static final byte[] CLASS_BYTES_B = Base64.getDecoder().decode(
+    "yv66vgAAADQAHAoABgAOCQAPABAIABEKABIAEwcAFAcAFQEABjxpbml0PgEAAygpVgEABENvZGUB" +
+    "AA9MaW5lTnVtYmVyVGFibGUBAAVzYXlIaQEAClNvdXJjZUZpbGUBAA5UcmFuc2Zvcm0uamF2YQwA" +
+    "BwAIBwAWDAAXABgBAAdHb29kYnllBwAZDAAaABsBAAlUcmFuc2Zvcm0BABBqYXZhL2xhbmcvT2Jq" +
+    "ZWN0AQAQamF2YS9sYW5nL1N5c3RlbQEAA291dAEAFUxqYXZhL2lvL1ByaW50U3RyZWFtOwEAE2ph" +
+    "dmEvaW8vUHJpbnRTdHJlYW0BAAdwcmludGxuAQAVKExqYXZhL2xhbmcvU3RyaW5nOylWACAABQAG" +
+    "AAAAAAACAAAABwAIAAEACQAAAB0AAQABAAAABSq3AAGxAAAAAQAKAAAABgABAAAAEQABAAsACAAB" +
+    "AAkAAAAlAAIAAQAAAAmyAAISA7YABLEAAAABAAoAAAAKAAIAAAATAAgAFAABAAwAAAACAA0=");
+  private static final byte[] DEX_BYTES_B = Base64.getDecoder().decode(
+    "ZGV4CjAzNQCLXSBQ5FiS3f16krSYZFF8xYZtFVp0GRXMAgAAcAAAAHhWNBIAAAAAAAAAACwCAAAO" +
+    "AAAAcAAAAAYAAACoAAAAAgAAAMAAAAABAAAA2AAAAAQAAADgAAAAAQAAAAABAACsAQAAIAEAAGIB" +
+    "AABqAQAAcwEAAIABAACXAQAAqwEAAL8BAADTAQAA4wEAAOYBAADqAQAA/gEAAAMCAAAMAgAAAgAA" +
+    "AAMAAAAEAAAABQAAAAYAAAAIAAAACAAAAAUAAAAAAAAACQAAAAUAAABcAQAABAABAAsAAAAAAAAA" +
+    "AAAAAAAAAAANAAAAAQABAAwAAAACAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAHAAAAAAAAAB4CAAAA" +
+    "AAAAAQABAAEAAAATAgAABAAAAHAQAwAAAA4AAwABAAIAAAAYAgAACQAAAGIAAAAbAQEAAABuIAIA" +
+    "EAAOAAAAAQAAAAMABjxpbml0PgAHR29vZGJ5ZQALTFRyYW5zZm9ybTsAFUxqYXZhL2lvL1ByaW50" +
+    "U3RyZWFtOwASTGphdmEvbGFuZy9PYmplY3Q7ABJMamF2YS9sYW5nL1N0cmluZzsAEkxqYXZhL2xh" +
+    "bmcvU3lzdGVtOwAOVHJhbnNmb3JtLmphdmEAAVYAAlZMABJlbWl0dGVyOiBqYWNrLTMuMzYAA291" +
+    "dAAHcHJpbnRsbgAFc2F5SGkAEQAHDgATAAcOhQAAAAEBAICABKACAQG4Ag0AAAAAAAAAAQAAAAAA" +
+    "AAABAAAADgAAAHAAAAACAAAABgAAAKgAAAADAAAAAgAAAMAAAAAEAAAAAQAAANgAAAAFAAAABAAA" +
+    "AOAAAAAGAAAAAQAAAAABAAABIAAAAgAAACABAAABEAAAAQAAAFwBAAACIAAADgAAAGIBAAADIAAA" +
+    "AgAAABMCAAAAIAAAAQAAAB4CAAAAEAAAAQAAACwCAAA=");
+
+  public static void main(String[] args) {
+    doTest(new Transform());
+  }
+
+  public static void doTest(Transform t) {
+    // TODO We currently need to do this transform call since we don't have any way to make the
+    // original-dex-file a single-class dex-file letting us restore it easily. We should use the
+    // manipulation library that is being made when we store the original dex file.
+    // TODO REMOVE this theoretically does nothing but it ensures the original-dex-file we have set
+    // is one we can return to unaltered.
+    doCommonClassRedefinition(Transform.class, CLASS_BYTES_A, DEX_BYTES_A);
+    t.sayHi();
+
+    // Now turn it into DEX_BYTES_B so it says 'Goodbye'
+    addCommonTransformationResult("Transform", CLASS_BYTES_B, DEX_BYTES_B);
+    enableCommonRetransformation(true);
+    doCommonClassRetransformation(Transform.class);
+    t.sayHi();
+
+    // Now turn it back to normal by removing the load-hook and transforming again.
+    enableCommonRetransformation(false);
+    doCommonClassRetransformation(Transform.class);
+    t.sayHi();
+  }
+
+  // Transforms the class
+  private static native void doCommonClassRedefinition(Class<?> target,
+                                                       byte[] class_bytes,
+                                                       byte[] dex_bytes);
+  private static native void doCommonClassRetransformation(Class<?>... target);
+  private static native void enableCommonRetransformation(boolean enable);
+  private static native void addCommonTransformationResult(String target_name,
+                                                           byte[] class_bytes,
+                                                           byte[] dex_bytes);
+}
diff --git a/test/932-transform-saves/src/Transform.java b/test/932-transform-saves/src/Transform.java
new file mode 100644
index 0000000..8e8af35
--- /dev/null
+++ b/test/932-transform-saves/src/Transform.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+class Transform {
+  public void sayHi() {
+    // Use lower 'h' to make sure the string will have a different string id
+    // than the transformation (the transformation code is the same except
+    // the actual printed String, which was making the test inacurately passing
+    // in JIT mode when loading the string from the dex cache, as the string ids
+    // of the two different strings were the same).
+    // We know the string ids will be different because lexicographically:
+    // "Goodbye" < "LTransform;" < "hello".
+    System.out.println("hello");
+  }
+}
diff --git a/test/933-misc-events/build b/test/933-misc-events/build
new file mode 100755
index 0000000..898e2e5
--- /dev/null
+++ b/test/933-misc-events/build
@@ -0,0 +1,17 @@
+#!/bin/bash
+#
+# Copyright 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+./default-build "$@" --experimental agents
diff --git a/test/933-misc-events/expected.txt b/test/933-misc-events/expected.txt
new file mode 100644
index 0000000..024c560
--- /dev/null
+++ b/test/933-misc-events/expected.txt
@@ -0,0 +1,2 @@
+Received dump request.
+Done
diff --git a/test/933-misc-events/info.txt b/test/933-misc-events/info.txt
new file mode 100644
index 0000000..875a5f6
--- /dev/null
+++ b/test/933-misc-events/info.txt
@@ -0,0 +1 @@
+Tests basic functions in the jvmti plugin.
diff --git a/test/933-misc-events/misc_events.cc b/test/933-misc-events/misc_events.cc
new file mode 100644
index 0000000..860d4b5
--- /dev/null
+++ b/test/933-misc-events/misc_events.cc
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <atomic>
+#include <signal.h>
+#include <sys/types.h>
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "jni.h"
+#include "openjdkjvmti/jvmti.h"
+
+#include "ti-agent/common_helper.h"
+#include "ti-agent/common_load.h"
+
+namespace art {
+namespace Test933MiscEvents {
+
+static std::atomic<bool> saw_dump_request(false);
+
+static void DumpRequestCallback(jvmtiEnv* jenv ATTRIBUTE_UNUSED) {
+  printf("Received dump request.\n");
+  saw_dump_request.store(true, std::memory_order::memory_order_relaxed);
+}
+
+extern "C" JNIEXPORT void JNICALL Java_Main_testSigQuit(
+    JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED) {
+  jvmtiEventCallbacks callbacks;
+  memset(&callbacks, 0, sizeof(jvmtiEventCallbacks));
+  callbacks.DataDumpRequest = DumpRequestCallback;
+  jvmtiError ret = jvmti_env->SetEventCallbacks(&callbacks, sizeof(callbacks));
+  if (JvmtiErrorToException(env, ret)) {
+    return;
+  }
+
+  ret = jvmti_env->SetEventNotificationMode(JVMTI_ENABLE,
+                                            JVMTI_EVENT_DATA_DUMP_REQUEST,
+                                            nullptr);
+  if (JvmtiErrorToException(env, ret)) {
+    return;
+  }
+
+  // Send sigquit to self.
+  kill(getpid(), SIGQUIT);
+
+  // Busy-wait for request.
+  for (;;) {
+    sleep(1);
+    if (saw_dump_request.load(std::memory_order::memory_order_relaxed)) {
+      break;
+    }
+  }
+
+  ret = jvmti_env->SetEventNotificationMode(JVMTI_DISABLE, JVMTI_EVENT_DATA_DUMP_REQUEST, nullptr);
+  JvmtiErrorToException(env, ret);
+}
+
+}  // namespace Test933MiscEvents
+}  // namespace art
diff --git a/test/933-misc-events/run b/test/933-misc-events/run
new file mode 100755
index 0000000..0a8d067
--- /dev/null
+++ b/test/933-misc-events/run
@@ -0,0 +1,23 @@
+#!/bin/bash
+#
+# Copyright 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# This test checks whether dex files can be injected into parent classloaders. App images preload
+# classes, which will make the injection moot. Turn off app images to avoid the issue.
+
+./default-run "$@" --experimental agents \
+                   --experimental runtime-plugins \
+                   --jvmti \
+                   --no-app-image
diff --git a/test/933-misc-events/src/Main.java b/test/933-misc-events/src/Main.java
new file mode 100644
index 0000000..89801a3
--- /dev/null
+++ b/test/933-misc-events/src/Main.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+public class Main {
+  public static void main(String[] args) throws Exception {
+    testSigQuit();
+
+    System.out.println("Done");
+  }
+
+  private static native void testSigQuit();
+}
diff --git a/test/957-methodhandle-transforms/expected.txt b/test/957-methodhandle-transforms/expected.txt
index 7540ef7..154051f 100644
--- a/test/957-methodhandle-transforms/expected.txt
+++ b/test/957-methodhandle-transforms/expected.txt
@@ -16,3 +16,34 @@
 fallback: fallback, 42, 56
 target: target, 42, 56
 target: target, 42, 56
+a: a, b:b, c: c
+a: a, b:b, c: c
+a: a, b:b, c: c
+a: a, b:b, c: c
+a: a, b:b, c: c
+a: a, b:b, c: c
+a: a, b:b, c: c
+a: a, b:43
+a: a, b:43
+a: a, b:43
+a: a, b:43
+a: a, b:43
+a: a, b:b, c: c
+a: a, b:b, c: c
+a: a, b:b, c: c
+a: a, b:true, c: false
+a: a, b:true, c: false
+a: a, b:1, c: 2, d: 3, e: 4, f:5, g: 6.0, h: 7.0
+a: a, b:1, c: 2, d: 3, e: 4, f:5, g: 6.0, h: 7.0
+a: a, b:1, c: 2, d: 51, e: 52, f:53.0, g: 54.0
+a: a, b:1, c: 2, d: 51, e: 52, f:53.0, g: 54.0
+a: a, b:1, c: 2, d: 3, e: 4, f:5.0, g:6.0
+a: a, b:1, c: 2, d: 3, e: 4, f:5.0, g:6.0
+a: a, b:1, c: 2, d: 3, e: 4.0, f:5.0
+a: a, b:1, c: 2, d: 3, e: 4.0, f:5.0
+a: a, b:1, c: 2, d: 3.0, e: 4.0
+a: a, b:1, c: 2, d: 3.0, e: 4.0
+a: a, b:1.0, c: 2.0, d: 3.0
+a: a, b:1.0, c: 2.0, d: 3.0
+a: a, b:1.0, c: 2.0
+a: a, b:1.0, c: 2.0
diff --git a/test/957-methodhandle-transforms/src/Main.java b/test/957-methodhandle-transforms/src/Main.java
index eebf55f..3271108 100644
--- a/test/957-methodhandle-transforms/src/Main.java
+++ b/test/957-methodhandle-transforms/src/Main.java
@@ -34,6 +34,8 @@
     testFilterReturnValue();
     testPermuteArguments();
     testInvokers();
+    testSpreaders_reference();
+    testSpreaders_primitive();
   }
 
   public static void testThrowException() throws Throwable {
@@ -921,6 +923,306 @@
     }
   }
 
+  public static int spreadReferences(String a, String b, String c) {
+    System.out.println("a: " + a + ", b:" + b + ", c: " + c);
+    return 42;
+  }
+
+  public static int spreadReferences_Unbox(String a, int b) {
+    System.out.println("a: " + a + ", b:" + b);
+    return 43;
+  }
+
+  public static void testSpreaders_reference() throws Throwable {
+    MethodType methodType = MethodType.methodType(int.class,
+        new Class<?>[] { String.class, String.class, String.class });
+    MethodHandle delegate = MethodHandles.lookup().findStatic(
+        Main.class, "spreadReferences", methodType);
+
+    // Basic checks on array lengths.
+    //
+    // Array size = 0
+    MethodHandle mhAsSpreader = delegate.asSpreader(String[].class, 0);
+    int ret = (int) mhAsSpreader.invoke("a", "b", "c", new String[] {});
+    assertEquals(42, ret);
+    // Array size = 1
+    mhAsSpreader = delegate.asSpreader(String[].class, 1);
+    ret = (int) mhAsSpreader.invoke("a", "b", new String[] { "c" });
+    assertEquals(42, ret);
+    // Array size = 2
+    mhAsSpreader = delegate.asSpreader(String[].class, 2);
+    ret = (int) mhAsSpreader.invoke("a", new String[] { "b", "c" });
+    assertEquals(42, ret);
+    // Array size = 3
+    mhAsSpreader = delegate.asSpreader(String[].class, 3);
+    ret = (int) mhAsSpreader.invoke(new String[] { "a", "b", "c"});
+    assertEquals(42, ret);
+
+    // Exception case, array size = 4 is illegal.
+    try {
+      delegate.asSpreader(String[].class, 4);
+      fail();
+    } catch (IllegalArgumentException expected) {
+    }
+
+    // Exception case, calling with an arg of the wrong size.
+    // Array size = 3
+    mhAsSpreader = delegate.asSpreader(String[].class, 3);
+    try {
+      ret = (int) mhAsSpreader.invoke(new String[] { "a", "b"});
+    } catch (IllegalArgumentException expected) {
+    }
+
+    // Various other hijinks, pass as Object[] arrays, Object etc.
+    mhAsSpreader = delegate.asSpreader(Object[].class, 2);
+    ret = (int) mhAsSpreader.invoke("a", new String[] { "b", "c" });
+    assertEquals(42, ret);
+
+    mhAsSpreader = delegate.asSpreader(Object[].class, 2);
+    ret = (int) mhAsSpreader.invoke("a", new Object[] { "b", "c" });
+    assertEquals(42, ret);
+
+    mhAsSpreader = delegate.asSpreader(Object[].class, 2);
+    ret = (int) mhAsSpreader.invoke("a", (Object) new Object[] { "b", "c" });
+    assertEquals(42, ret);
+
+    // Test implicit unboxing.
+    MethodType methodType2 = MethodType.methodType(int.class,
+        new Class<?>[] { String.class, int.class });
+    MethodHandle delegate2 = MethodHandles.lookup().findStatic(
+        Main.class, "spreadReferences_Unbox", methodType2);
+
+    // .. with an Integer[] array.
+    mhAsSpreader = delegate2.asSpreader(Integer[].class, 1);
+    ret = (int) mhAsSpreader.invoke("a", new Integer[] { 43 });
+    assertEquals(43, ret);
+
+    // .. with an Integer[] array declared as an Object[] argument type.
+    mhAsSpreader = delegate2.asSpreader(Object[].class, 1);
+    ret = (int) mhAsSpreader.invoke("a", new Integer[] { 43 });
+    assertEquals(43, ret);
+
+    // .. with an Object[] array.
+    mhAsSpreader = delegate2.asSpreader(Object[].class, 1);
+    ret = (int) mhAsSpreader.invoke("a", new Object[] { Integer.valueOf(43)});
+    assertEquals(43, ret);
+
+    // -- Part 2--
+    // Run a subset of these tests on MethodHandles.spreadInvoker, which only accepts
+    // a trailing argument type of Object[].
+    MethodHandle spreadInvoker = MethodHandles.spreadInvoker(methodType2, 1);
+    ret = (int) spreadInvoker.invoke(delegate2, "a", new Object[] { Integer.valueOf(43)});
+    assertEquals(43, ret);
+
+    ret = (int) spreadInvoker.invoke(delegate2, "a", new Integer[] { 43 });
+    assertEquals(43, ret);
+
+    // NOTE: Annoyingly, the second argument here is leadingArgCount and not
+    // arrayLength.
+    spreadInvoker = MethodHandles.spreadInvoker(methodType, 3);
+    ret = (int) spreadInvoker.invoke(delegate, "a", "b", "c", new String[] {});
+    assertEquals(42, ret);
+
+    spreadInvoker = MethodHandles.spreadInvoker(methodType, 0);
+    ret = (int) spreadInvoker.invoke(delegate, new String[] { "a", "b", "c" });
+    assertEquals(42, ret);
+
+    // Exact invokes: Double check that the expected parameter type is
+    // Object[] and not T[].
+    try {
+      spreadInvoker.invokeExact(delegate, new String[] { "a", "b", "c" });
+      fail();
+    } catch (WrongMethodTypeException expected) {
+    }
+
+    ret = (int) spreadInvoker.invoke(delegate, new Object[] { "a", "b", "c" });
+    assertEquals(42, ret);
+  }
+
+  public static int spreadBoolean(String a, Boolean b, boolean c) {
+    System.out.println("a: " + a + ", b:" + b + ", c: " + c);
+    return 44;
+  }
+
+  public static int spreadByte(String a, Byte b, byte c,
+      short d, int e, long f, float g, double h) {
+    System.out.println("a: " + a + ", b:" + b + ", c: " + c +
+        ", d: " + d + ", e: " + e + ", f:" + f + ", g: " + g +
+        ", h: " + h);
+    return 45;
+  }
+
+  public static int spreadChar(String a, Character b, char c,
+      int d, long e, float f, double g) {
+    System.out.println("a: " + a + ", b:" + b + ", c: " + c +
+        ", d: " + d + ", e: " + e + ", f:" + f + ", g: " + g);
+    return 46;
+  }
+
+  public static int spreadShort(String a, Short b, short c,
+      int d, long e, float f, double g) {
+    System.out.println("a: " + a + ", b:" + b + ", c: " + c +
+        ", d: " + d + ", e: " + e + ", f:" + f + ", g:" + g);
+    return 47;
+  }
+
+  public static int spreadInt(String a, Integer b, int c,
+      long d, float e, double f) {
+    System.out.println("a: " + a + ", b:" + b + ", c: " + c +
+        ", d: " + d + ", e: " + e + ", f:" + f);
+    return 48;
+  }
+
+  public static int spreadLong(String a, Long b, long c, float d, double e) {
+    System.out.println("a: " + a + ", b:" + b + ", c: " + c +
+        ", d: " + d + ", e: " + e);
+    return 49;
+  }
+
+  public static int spreadFloat(String a, Float b, float c, double d) {
+    System.out.println("a: " + a + ", b:" + b + ", c: " + c + ", d: " + d);
+    return 50;
+  }
+
+  public static int spreadDouble(String a, Double b, double c) {
+    System.out.println("a: " + a + ", b:" + b + ", c: " + c);
+    return 51;
+  }
+
+  public static void testSpreaders_primitive() throws Throwable {
+    // boolean[]
+    // ---------------------
+    MethodType type = MethodType.methodType(int.class,
+        new Class<?>[] { String.class, Boolean.class, boolean.class });
+    MethodHandle delegate = MethodHandles.lookup().findStatic(
+        Main.class, "spreadBoolean", type);
+
+    MethodHandle spreader = delegate.asSpreader(boolean[].class, 2);
+    int ret = (int) spreader.invokeExact("a", new boolean[] { true, false });
+    assertEquals(44, ret);
+    ret = (int) spreader.invoke("a", new boolean[] { true, false });
+    assertEquals(44, ret);
+
+    // boolean can't be cast to String (the first argument to the method).
+    try {
+      delegate.asSpreader(boolean[].class, 3);
+      fail();
+    } catch (WrongMethodTypeException expected) {
+    }
+
+    // int can't be cast to boolean to supply the last argument to the method.
+    try {
+      delegate.asSpreader(int[].class, 1);
+      fail();
+    } catch (WrongMethodTypeException expected) {
+    }
+
+    // byte[]
+    // ---------------------
+    type = MethodType.methodType(int.class,
+        new Class<?>[] {
+          String.class, Byte.class, byte.class,
+          short.class, int.class, long.class,
+          float.class, double.class });
+    delegate = MethodHandles.lookup().findStatic(Main.class, "spreadByte", type);
+
+    spreader = delegate.asSpreader(byte[].class, 7);
+    ret = (int) spreader.invokeExact("a",
+        new byte[] { 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7 });
+    assertEquals(45, ret);
+    ret = (int) spreader.invoke("a",
+        new byte[] { 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7 });
+    assertEquals(45, ret);
+
+    // char[]
+    // ---------------------
+    type = MethodType.methodType(int.class,
+        new Class<?>[] {
+          String.class, Character.class,char.class,
+          int.class, long.class, float.class, double.class });
+    delegate = MethodHandles.lookup().findStatic(Main.class, "spreadChar", type);
+
+    spreader = delegate.asSpreader(char[].class, 6);
+    ret = (int) spreader.invokeExact("a",
+        new char[] { '1', '2', '3', '4', '5', '6' });
+    assertEquals(46, ret);
+    ret = (int) spreader.invokeExact("a",
+        new char[] { '1', '2', '3', '4', '5', '6' });
+    assertEquals(46, ret);
+
+    // short[]
+    // ---------------------
+    type = MethodType.methodType(int.class,
+        new Class<?>[] {
+          String.class, Short.class, short.class,
+          int.class, long.class, float.class, double.class });
+    delegate = MethodHandles.lookup().findStatic(Main.class, "spreadShort", type);
+
+    spreader = delegate.asSpreader(short[].class, 6);
+    ret = (int) spreader.invokeExact("a",
+        new short[] { 0x1, 0x2, 0x3, 0x4, 0x5, 0x6 });
+    assertEquals(47, ret);
+    ret = (int) spreader.invoke("a",
+        new short[] { 0x1, 0x2, 0x3, 0x4, 0x5, 0x6 });
+    assertEquals(47, ret);
+
+    // int[]
+    // ---------------------
+    type = MethodType.methodType(int.class,
+        new Class<?>[] {
+          String.class, Integer.class, int.class,
+          long.class, float.class, double.class });
+    delegate = MethodHandles.lookup().findStatic(Main.class, "spreadInt", type);
+
+    spreader = delegate.asSpreader(int[].class, 5);
+    ret = (int) spreader.invokeExact("a", new int[] { 1, 2, 3, 4, 5 });
+    assertEquals(48, ret);
+    ret = (int) spreader.invokeExact("a", new int[] { 1, 2, 3, 4, 5 });
+    assertEquals(48, ret);
+
+    // long[]
+    // ---------------------
+    type = MethodType.methodType(int.class,
+        new Class<?>[] {
+          String.class, Long.class, long.class, float.class, double.class });
+    delegate = MethodHandles.lookup().findStatic(Main.class, "spreadLong", type);
+
+    spreader = delegate.asSpreader(long[].class, 4);
+    ret = (int) spreader.invokeExact("a",
+        new long[] { 0x1, 0x2, 0x3, 0x4 });
+    assertEquals(49, ret);
+    ret = (int) spreader.invoke("a",
+        new long[] { 0x1, 0x2, 0x3, 0x4 });
+    assertEquals(49, ret);
+
+    // float[]
+    // ---------------------
+    type = MethodType.methodType(int.class,
+        new Class<?>[] {
+          String.class, Float.class, float.class, double.class });
+    delegate = MethodHandles.lookup().findStatic(Main.class, "spreadFloat", type);
+
+    spreader = delegate.asSpreader(float[].class, 3);
+    ret = (int) spreader.invokeExact("a",
+        new float[] { 1.0f, 2.0f, 3.0f });
+    assertEquals(50, ret);
+    ret = (int) spreader.invokeExact("a",
+        new float[] { 1.0f, 2.0f, 3.0f });
+    assertEquals(50, ret);
+
+    // double[]
+    // ---------------------
+    type = MethodType.methodType(int.class,
+        new Class<?>[] { String.class, Double.class, double.class });
+    delegate = MethodHandles.lookup().findStatic(Main.class, "spreadDouble", type);
+
+    spreader = delegate.asSpreader(double[].class, 2);
+    ret = (int) spreader.invokeExact("a", new double[] { 1.0, 2.0 });
+    assertEquals(51, ret);
+    ret = (int) spreader.invokeExact("a", new double[] { 1.0, 2.0 });
+    assertEquals(51, ret);
+  }
+
   public static void fail() {
     System.out.println("FAIL");
     Thread.dumpStack();
@@ -931,6 +1233,10 @@
     Thread.dumpStack();
   }
 
+  public static void assertEquals(int i1, int i2) {
+    if (i1 != i2) throw new AssertionError("Expected: " + i1 + " was " + i2);
+  }
+
   public static void assertEquals(String s1, String s2) {
     if (s1 == s2) {
       return;
diff --git a/test/Android.bp b/test/Android.bp
index 965d07a..287df13 100644
--- a/test/Android.bp
+++ b/test/Android.bp
@@ -173,12 +173,13 @@
     whole_static_libs: [
         "libart-compiler-gtest",
         "libart-runtime-gtest",
-        "libgtest",
+        "libgtest"
     ],
     shared_libs: [
         "libartd",
         "libartd-compiler",
         "libbase",
+        "libbacktrace"
     ],
     target: {
         android: {
@@ -269,6 +270,8 @@
         "927-timers/timers.cc",
         "928-jni-table/jni_table.cc",
         "929-search/search.cc",
+        "931-agent-thread/agent_thread.cc",
+        "933-misc-events/misc_events.cc",
     ],
     shared_libs: [
         "libbase",
@@ -315,6 +318,7 @@
         "141-class-unload/jni_unload.cc",
         "148-multithread-gc-annotations/gc_coverage.cc",
         "149-suspend-all-stress/suspend_all.cc",
+        "154-gc-loop/heap_interface.cc",
         "454-get-vreg/get_vreg_jni.cc",
         "457-regs/regs_jni.cc",
         "461-get-reference-vreg/get_reference_vreg_jni.cc",
@@ -325,6 +329,7 @@
         "570-checker-osr/osr.cc",
         "595-profile-saving/profile-saving.cc",
         "596-app-images/app_images.cc",
+        "596-monitor-inflation/monitor_inflation.cc",
         "597-deopt-new-string/deopt.cc",
         "626-const-class-linking/clear_dex_cache_types.cc",
     ],
diff --git a/test/Android.run-test.mk b/test/Android.run-test.mk
index e604c93..cb798f0 100644
--- a/test/Android.run-test.mk
+++ b/test/Android.run-test.mk
@@ -277,39 +277,6 @@
   147-stripped-dex-fallback \
   569-checker-pattern-replacement
 
-# These 9** tests are not supported in current form due to linker
-# restrictions. See b/31681198
-TEST_ART_BROKEN_TARGET_TESTS += \
-  901-hello-ti-agent \
-  902-hello-transformation \
-  903-hello-tagging \
-  904-object-allocation \
-  905-object-free \
-  906-iterate-heap \
-  907-get-loaded-classes \
-  908-gc-start-finish \
-  909-attach-agent \
-  910-methods \
-  911-get-stack-trace \
-  912-classes \
-  913-heaps \
-  914-hello-obsolescence \
-  915-obsolete-2 \
-  916-obsolete-jit \
-  917-fields-transformation \
-  918-fields \
-  919-obsolete-fields \
-  920-objects \
-  921-hello-failure \
-  922-properties \
-  923-monitors \
-  924-threads \
-  925-threadgroups \
-  926-multi-obsolescence \
-  927-timers \
-  928-jni-table \
-  929-search \
-
 ifneq (,$(filter target,$(TARGET_TYPES)))
   ART_TEST_KNOWN_BROKEN += $(call all-run-test-names,target,$(RUN_TYPES),$(PREBUILD_TYPES), \
       $(COMPILER_TYPES),$(RELOCATE_TYPES),$(TRACE_TYPES),$(GC_TYPES),$(JNI_TYPES), \
@@ -412,6 +379,7 @@
 #   slows down allocations significantly which these tests do a lot.
 TEST_ART_BROKEN_GCSTRESS_RUN_TESTS := \
   137-cfi \
+  154-gc-loop \
   908-gc-start-finish \
   913-heaps \
   961-default-iface-resolution-gen \
@@ -569,12 +537,14 @@
 # flaky as JIT tests. This should be fixed once b/33630159 or b/33616143 are
 # resolved but until then just disable them. Test 916 already checks this
 # feature for JIT use cases in a way that is resilient to the jit frames.
+# 912: b/34655682
 TEST_ART_BROKEN_JIT_RUN_TESTS := \
   137-cfi \
   629-vdex-speed \
   902-hello-transformation \
   904-object-allocation \
   906-iterate-heap \
+  912-classes \
   914-hello-obsolescence \
   915-obsolete-2 \
   917-fields-transformation \
diff --git a/test/ErroneousInit/ErroneousInit.java b/test/ErroneousInit/ErroneousInit.java
new file mode 100644
index 0000000..67b7b20
--- /dev/null
+++ b/test/ErroneousInit/ErroneousInit.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+class ErroneousInit {
+    static {
+        if (true) {
+            throw new Error();
+        }
+    }
+}
diff --git a/test/XandY/Y.java b/test/XandY/Y.java
index ecead6e..2a1f036 100644
--- a/test/XandY/Y.java
+++ b/test/XandY/Y.java
@@ -14,4 +14,8 @@
  * limitations under the License.
  */
 
-class Y extends X {}
+class Y extends X {
+  static Z z = new Z();
+  static class Z {
+  }
+}
diff --git a/test/etc/run-test-jar b/test/etc/run-test-jar
index 5f1071f..28fa130 100755
--- a/test/etc/run-test-jar
+++ b/test/etc/run-test-jar
@@ -44,7 +44,7 @@
 SECONDARY_DEX=""
 TIME_OUT="gdb"  # "n" (disabled), "timeout" (use timeout), "gdb" (use gdb)
 # Value in seconds
-if [ "$ART_USE_READ_BARRIER" = "true" ]; then
+if [ "$ART_USE_READ_BARRIER" != "false" ]; then
   TIME_OUT_VALUE=2400  # 40 minutes.
 else
   TIME_OUT_VALUE=1200  # 20 minutes.
diff --git a/test/run-test b/test/run-test
index a913e78..a228789 100755
--- a/test/run-test
+++ b/test/run-test
@@ -131,6 +131,7 @@
 multi_image_suffix=""
 android_root="/system"
 bisection_search="no"
+suspend_timeout="500000"
 # By default we will use optimizing.
 image_args=""
 image_suffix=""
@@ -219,6 +220,10 @@
         basic_verify="true"
         gc_stress="true"
         shift
+    elif [ "x$1" = "x--suspend-timeout" ]; then
+        shift
+        suspend_timeout="$1"
+        shift
     elif [ "x$1" = "x--image" ]; then
         shift
         image="$1"
@@ -402,6 +407,11 @@
 tmp_dir="`cd $oldwd ; python -c "import os; print os.path.realpath('$tmp_dir')"`"
 mkdir -p $tmp_dir
 
+# Add thread suspend timeout flag
+if [ ! "$runtime" = "jvm" ]; then
+  run_args="${run_args} --runtime-option -XX:ThreadSuspendTimeout=$suspend_timeout"
+fi
+
 if [ "$basic_verify" = "true" ]; then
   # Set HspaceCompactForOOMMinIntervalMs to zero to run hspace compaction for OOM more frequently in tests.
   run_args="${run_args} --runtime-option -Xgc:preverify --runtime-option -Xgc:postverify --runtime-option -XX:HspaceCompactForOOMMinIntervalMs=0"
@@ -649,6 +659,7 @@
         echo "    --quiet               Don't print anything except failure messages"
         echo "    --bisection-search    Perform bisection bug search."
         echo "    --vdex                Test using vdex as in input to dex2oat. Only works with --prebuild."
+        echo "    --suspend-timeout     Change thread suspend timeout ms (default 500000)."
     ) 1>&2  # Direct to stderr so usage is not printed if --quiet is set.
     exit 1
 fi
@@ -722,7 +733,7 @@
     #
     # TODO: Enable Checker when read barrier support is added to more
     # architectures (b/12687968).
-    if [ "x$ART_USE_READ_BARRIER" = xtrue ]                    \
+    if [ "x$ART_USE_READ_BARRIER" != xfalse ]                  \
        && (([ "x$host_mode" = "xyes" ]                         \
             && ! arch_supports_read_barrier "$host_arch_name") \
            || ([ "x$target_mode" = "xyes" ]                    \
diff --git a/test/ti-agent/common_helper.cc b/test/ti-agent/common_helper.cc
index 2c6d3ed..80e1797 100644
--- a/test/ti-agent/common_helper.cc
+++ b/test/ti-agent/common_helper.cc
@@ -16,13 +16,18 @@
 
 #include "ti-agent/common_helper.h"
 
+#include <dlfcn.h>
 #include <stdio.h>
 #include <sstream>
+#include <deque>
 
+#include "android-base/stringprintf.h"
 #include "art_method.h"
 #include "jni.h"
+#include "jni_internal.h"
 #include "openjdkjvmti/jvmti.h"
 #include "scoped_thread_state_change-inl.h"
+#include "ScopedLocalRef.h"
 #include "stack.h"
 #include "ti-agent/common_load.h"
 #include "utils.h"
@@ -60,17 +65,17 @@
   return true;
 }
 
-namespace common_redefine {
 
-static void throwRedefinitionError(jvmtiEnv* jvmti,
-                                   JNIEnv* env,
-                                   jint num_targets,
-                                   jclass* target,
-                                   jvmtiError res) {
+template <bool is_redefine>
+static void throwCommonRedefinitionError(jvmtiEnv* jvmti,
+                                         JNIEnv* env,
+                                         jint num_targets,
+                                         jclass* target,
+                                         jvmtiError res) {
   std::stringstream err;
   char* error = nullptr;
   jvmti->GetErrorName(res, &error);
-  err << "Failed to redefine class";
+  err << "Failed to " << (is_redefine ? "redefine" : "retransform") << " class";
   if (num_targets > 1) {
     err << "es";
   }
@@ -92,6 +97,16 @@
   env->ThrowNew(env->FindClass("java/lang/Exception"), message.c_str());
 }
 
+namespace common_redefine {
+
+static void throwRedefinitionError(jvmtiEnv* jvmti,
+                                   JNIEnv* env,
+                                   jint num_targets,
+                                   jclass* target,
+                                   jvmtiError res) {
+  return throwCommonRedefinitionError<true>(jvmti, env, num_targets, target, res);
+}
+
 static void DoMultiClassRedefine(jvmtiEnv* jvmti_env,
                                  JNIEnv* env,
                                  jint num_redefines,
@@ -161,7 +176,139 @@
                               dex_files.data());
 }
 
-// Don't do anything
+// Get all capabilities except those related to retransformation.
+jint OnLoad(JavaVM* vm,
+            char* options ATTRIBUTE_UNUSED,
+            void* reserved ATTRIBUTE_UNUSED) {
+  if (vm->GetEnv(reinterpret_cast<void**>(&jvmti_env), JVMTI_VERSION_1_0)) {
+    printf("Unable to get jvmti env!\n");
+    return 1;
+  }
+  jvmtiCapabilities caps;
+  jvmti_env->GetPotentialCapabilities(&caps);
+  caps.can_retransform_classes = 0;
+  caps.can_retransform_any_class = 0;
+  jvmti_env->AddCapabilities(&caps);
+  return 0;
+}
+
+}  // namespace common_redefine
+
+namespace common_retransform {
+
+struct CommonTransformationResult {
+  std::vector<unsigned char> class_bytes;
+  std::vector<unsigned char> dex_bytes;
+
+  CommonTransformationResult(size_t class_size, size_t dex_size)
+      : class_bytes(class_size), dex_bytes(dex_size) {}
+
+  CommonTransformationResult() = default;
+  CommonTransformationResult(CommonTransformationResult&&) = default;
+  CommonTransformationResult(CommonTransformationResult&) = default;
+};
+
+// Map from class name to transformation result.
+std::map<std::string, std::deque<CommonTransformationResult>> gTransformations;
+
+extern "C" JNIEXPORT void JNICALL Java_Main_addCommonTransformationResult(JNIEnv* env,
+                                                                          jclass,
+                                                                          jstring class_name,
+                                                                          jbyteArray class_array,
+                                                                          jbyteArray dex_array) {
+  const char* name_chrs = env->GetStringUTFChars(class_name, nullptr);
+  std::string name_str(name_chrs);
+  env->ReleaseStringUTFChars(class_name, name_chrs);
+  CommonTransformationResult trans(env->GetArrayLength(class_array),
+                                   env->GetArrayLength(dex_array));
+  if (env->ExceptionOccurred()) {
+    return;
+  }
+  env->GetByteArrayRegion(class_array,
+                          0,
+                          env->GetArrayLength(class_array),
+                          reinterpret_cast<jbyte*>(trans.class_bytes.data()));
+  if (env->ExceptionOccurred()) {
+    return;
+  }
+  env->GetByteArrayRegion(dex_array,
+                          0,
+                          env->GetArrayLength(dex_array),
+                          reinterpret_cast<jbyte*>(trans.dex_bytes.data()));
+  if (env->ExceptionOccurred()) {
+    return;
+  }
+  if (gTransformations.find(name_str) == gTransformations.end()) {
+    std::deque<CommonTransformationResult> list;
+    gTransformations[name_str] = std::move(list);
+  }
+  gTransformations[name_str].push_back(std::move(trans));
+}
+
+// The hook we are using.
+void JNICALL CommonClassFileLoadHookRetransformable(jvmtiEnv* jvmti_env,
+                                                    JNIEnv* jni_env ATTRIBUTE_UNUSED,
+                                                    jclass class_being_redefined ATTRIBUTE_UNUSED,
+                                                    jobject loader ATTRIBUTE_UNUSED,
+                                                    const char* name,
+                                                    jobject protection_domain ATTRIBUTE_UNUSED,
+                                                    jint class_data_len ATTRIBUTE_UNUSED,
+                                                    const unsigned char* class_dat ATTRIBUTE_UNUSED,
+                                                    jint* new_class_data_len,
+                                                    unsigned char** new_class_data) {
+  std::string name_str(name);
+  if (gTransformations.find(name_str) != gTransformations.end() &&
+      gTransformations[name_str].size() > 0) {
+    CommonTransformationResult& res = gTransformations[name_str][0];
+    const std::vector<unsigned char>& desired_array = IsJVM() ? res.class_bytes : res.dex_bytes;
+    unsigned char* new_data;
+    CHECK_EQ(JVMTI_ERROR_NONE, jvmti_env->Allocate(desired_array.size(), &new_data));
+    memcpy(new_data, desired_array.data(), desired_array.size());
+    *new_class_data = new_data;
+    *new_class_data_len = desired_array.size();
+    gTransformations[name_str].pop_front();
+  }
+}
+
+extern "C" JNIEXPORT void Java_Main_enableCommonRetransformation(JNIEnv* env,
+                                                                 jclass,
+                                                                 jboolean enable) {
+  jvmtiError res = jvmti_env->SetEventNotificationMode(enable ? JVMTI_ENABLE : JVMTI_DISABLE,
+                                                       JVMTI_EVENT_CLASS_FILE_LOAD_HOOK,
+                                                       nullptr);
+  if (res != JVMTI_ERROR_NONE) {
+    JvmtiErrorToException(env, res);
+  }
+}
+
+static void throwRetransformationError(jvmtiEnv* jvmti,
+                                       JNIEnv* env,
+                                       jint num_targets,
+                                       jclass* targets,
+                                       jvmtiError res) {
+  return throwCommonRedefinitionError<false>(jvmti, env, num_targets, targets, res);
+}
+
+static void DoClassRetransformation(jvmtiEnv* jvmti_env, JNIEnv* env, jobjectArray targets) {
+  std::vector<jclass> classes;
+  jint len = env->GetArrayLength(targets);
+  for (jint i = 0; i < len; i++) {
+    classes.push_back(static_cast<jclass>(env->GetObjectArrayElement(targets, i)));
+  }
+  jvmtiError res = jvmti_env->RetransformClasses(len, classes.data());
+  if (res != JVMTI_ERROR_NONE) {
+    throwRetransformationError(jvmti_env, env, len, classes.data(), res);
+  }
+}
+
+// TODO Write something useful.
+extern "C" JNIEXPORT void JNICALL Java_Main_doCommonClassRetransformation(JNIEnv* env,
+                                                                          jclass,
+                                                                          jobjectArray targets) {
+  DoClassRetransformation(jvmti_env, env, targets);
+}
+
+// Get all capabilities except those related to retransformation.
 jint OnLoad(JavaVM* vm,
             char* options ATTRIBUTE_UNUSED,
             void* reserved ATTRIBUTE_UNUSED) {
@@ -170,9 +317,135 @@
     return 1;
   }
   SetAllCapabilities(jvmti_env);
+  jvmtiEventCallbacks cb;
+  memset(&cb, 0, sizeof(cb));
+  cb.ClassFileLoadHook = CommonClassFileLoadHookRetransformable;
+  if (jvmti_env->SetEventCallbacks(&cb, sizeof(cb)) != JVMTI_ERROR_NONE) {
+    printf("Unable to set class file load hook cb!\n");
+    return 1;
+  }
   return 0;
 }
 
-}  // namespace common_redefine
+}  // namespace common_retransform
+
+static void BindMethod(jvmtiEnv* jenv,
+                       JNIEnv* env,
+                       jclass klass,
+                       jmethodID method) {
+  char* name;
+  char* signature;
+  jvmtiError name_result = jenv->GetMethodName(method, &name, &signature, nullptr);
+  if (name_result != JVMTI_ERROR_NONE) {
+    LOG(FATAL) << "Could not get methods";
+  }
+
+  ArtMethod* m = jni::DecodeArtMethod(method);
+
+  std::string names[2];
+  {
+    ScopedObjectAccess soa(Thread::Current());
+    names[0] = m->JniShortName();
+    names[1] = m->JniLongName();
+  }
+  for (const std::string& mangled_name : names) {
+    void* sym = dlsym(RTLD_DEFAULT, mangled_name.c_str());
+    if (sym == nullptr) {
+      continue;
+    }
+
+    JNINativeMethod native_method;
+    native_method.fnPtr = sym;
+    native_method.name = name;
+    native_method.signature = signature;
+
+    env->RegisterNatives(klass, &native_method, 1);
+
+    jenv->Deallocate(reinterpret_cast<unsigned char*>(name));
+    jenv->Deallocate(reinterpret_cast<unsigned char*>(signature));
+    return;
+  }
+
+  LOG(FATAL) << "Could not find " << names[0];
+}
+
+static jclass FindClassWithSystemClassLoader(JNIEnv* env, const char* class_name) {
+  // Find the system classloader.
+  ScopedLocalRef<jclass> cl_klass(env, env->FindClass("java/lang/ClassLoader"));
+  if (cl_klass.get() == nullptr) {
+    return nullptr;
+  }
+  jmethodID getsystemclassloader_method = env->GetStaticMethodID(cl_klass.get(),
+                                                                 "getSystemClassLoader",
+                                                                 "()Ljava/lang/ClassLoader;");
+  if (getsystemclassloader_method == nullptr) {
+    return nullptr;
+  }
+  ScopedLocalRef<jobject> cl(env, env->CallStaticObjectMethod(cl_klass.get(),
+                                                              getsystemclassloader_method));
+  if (cl.get() == nullptr) {
+    return nullptr;
+  }
+
+  // Create a String of the name.
+  std::string descriptor = android::base::StringPrintf("L%s;", class_name);
+  std::string dot_name = DescriptorToDot(descriptor.c_str());
+  ScopedLocalRef<jstring> name_str(env, env->NewStringUTF(dot_name.c_str()));
+
+  // Call Class.forName with it.
+  ScopedLocalRef<jclass> c_klass(env, env->FindClass("java/lang/Class"));
+  if (c_klass.get() == nullptr) {
+    return nullptr;
+  }
+  jmethodID forname_method = env->GetStaticMethodID(
+      c_klass.get(),
+      "forName",
+      "(Ljava/lang/String;ZLjava/lang/ClassLoader;)Ljava/lang/Class;");
+  if (forname_method == nullptr) {
+    return nullptr;
+  }
+
+  return reinterpret_cast<jclass>(env->CallStaticObjectMethod(c_klass.get(),
+                                                              forname_method,
+                                                              name_str.get(),
+                                                              JNI_FALSE,
+                                                              cl.get()));
+}
+
+void BindFunctions(jvmtiEnv* jenv, JNIEnv* env, const char* class_name) {
+  // Use JNI to load the class.
+  ScopedLocalRef<jclass> klass(env, env->FindClass(class_name));
+  if (klass.get() == nullptr) {
+    // We may be called with the wrong classloader. Try explicitly using the system classloader.
+    env->ExceptionClear();
+    klass.reset(FindClassWithSystemClassLoader(env, class_name));
+    if (klass.get() == nullptr) {
+      LOG(FATAL) << "Could not load " << class_name;
+    }
+  }
+
+  // Use JVMTI to get the methods.
+  jint method_count;
+  jmethodID* methods;
+  jvmtiError methods_result = jenv->GetClassMethods(klass.get(), &method_count, &methods);
+  if (methods_result != JVMTI_ERROR_NONE) {
+    LOG(FATAL) << "Could not get methods";
+  }
+
+  // Check each method.
+  for (jint i = 0; i < method_count; ++i) {
+    jint modifiers;
+    jvmtiError mod_result = jenv->GetMethodModifiers(methods[i], &modifiers);
+    if (mod_result != JVMTI_ERROR_NONE) {
+      LOG(FATAL) << "Could not get methods";
+    }
+    constexpr jint kNative = static_cast<jint>(kAccNative);
+    if ((modifiers & kNative) != 0) {
+      BindMethod(jenv, env, klass.get(), methods[i]);
+    }
+  }
+
+  jenv->Deallocate(reinterpret_cast<unsigned char*>(methods));
+}
 
 }  // namespace art
diff --git a/test/ti-agent/common_helper.h b/test/ti-agent/common_helper.h
index 642ca03..c60553d 100644
--- a/test/ti-agent/common_helper.h
+++ b/test/ti-agent/common_helper.h
@@ -27,6 +27,10 @@
 jint OnLoad(JavaVM* vm, char* options, void* reserved);
 
 }  // namespace common_redefine
+namespace common_retransform {
+jint OnLoad(JavaVM* vm, char* options, void* reserved);
+}  // namespace common_retransform
+
 
 extern bool RuntimeIsJVM;
 
@@ -67,6 +71,12 @@
 
 bool JvmtiErrorToException(JNIEnv* env, jvmtiError error);
 
+// Load the class through JNI. Inspect it, find all native methods. Construct the corresponding
+// mangled name, run dlsym and bind the method.
+//
+// This will abort on failure.
+void BindFunctions(jvmtiEnv* jvmti_env, JNIEnv* env, const char* class_name);
+
 }  // namespace art
 
 #endif  // ART_TEST_TI_AGENT_COMMON_HELPER_H_
diff --git a/test/ti-agent/common_load.cc b/test/ti-agent/common_load.cc
index 521e672..8ed8e67 100644
--- a/test/ti-agent/common_load.cc
+++ b/test/ti-agent/common_load.cc
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+#include "common_load.h"
+
 #include <jni.h>
 #include <stdio.h>
 // TODO I don't know?
@@ -22,7 +24,6 @@
 #include "art_method-inl.h"
 #include "base/logging.h"
 #include "base/macros.h"
-#include "common_load.h"
 #include "common_helper.h"
 
 #include "901-hello-ti-agent/basics.h"
@@ -32,6 +33,8 @@
 
 jvmtiEnv* jvmti_env;
 
+namespace {
+
 using OnLoad   = jint (*)(JavaVM* vm, char* options, void* reserved);
 using OnAttach = jint (*)(JavaVM* vm, char* options, void* reserved);
 
@@ -41,11 +44,50 @@
   OnAttach attach;
 };
 
+static void JNICALL VMInitCallback(jvmtiEnv *jvmti_env,
+                                   JNIEnv* jni_env,
+                                   jthread thread ATTRIBUTE_UNUSED) {
+  // Bind Main native methods.
+  BindFunctions(jvmti_env, jni_env, "Main");
+}
+
+// Install a phase callback that will bind JNI functions on VMInit.
+bool InstallBindCallback(JavaVM* vm) {
+  // Use a new jvmtiEnv. Otherwise we might collide with table changes.
+  jvmtiEnv* install_env;
+  if (vm->GetEnv(reinterpret_cast<void**>(&install_env), JVMTI_VERSION_1_0) != 0) {
+    return false;
+  }
+  SetAllCapabilities(install_env);
+
+  {
+    jvmtiEventCallbacks callbacks;
+    memset(&callbacks, 0, sizeof(jvmtiEventCallbacks));
+    callbacks.VMInit = VMInitCallback;
+
+    jvmtiError install_error = install_env->SetEventCallbacks(&callbacks, sizeof(callbacks));
+    if (install_error != JVMTI_ERROR_NONE) {
+      return false;
+    }
+  }
+
+  {
+    jvmtiError enable_error = install_env->SetEventNotificationMode(JVMTI_ENABLE,
+                                                                    JVMTI_EVENT_VM_INIT,
+                                                                    nullptr);
+    if (enable_error != JVMTI_ERROR_NONE) {
+      return false;
+    }
+  }
+
+  return true;
+}
+
 // A trivial OnLoad implementation that only initializes the global jvmti_env.
 static jint MinimalOnLoad(JavaVM* vm,
                           char* options ATTRIBUTE_UNUSED,
                           void* reserved ATTRIBUTE_UNUSED) {
-  if (vm->GetEnv(reinterpret_cast<void**>(&jvmti_env), JVMTI_VERSION_1_0)) {
+  if (vm->GetEnv(reinterpret_cast<void**>(&jvmti_env), JVMTI_VERSION_1_0) != 0) {
     printf("Unable to get jvmti env!\n");
     return 1;
   }
@@ -55,7 +97,7 @@
 
 // A list of all non-standard the agents we have for testing. All other agents will use
 // MinimalOnLoad.
-AgentLib agents[] = {
+static AgentLib agents[] = {
   { "901-hello-ti-agent", Test901HelloTi::OnLoad, nullptr },
   { "902-hello-transformation", common_redefine::OnLoad, nullptr },
   { "909-attach-agent", nullptr, Test909AttachAgent::OnAttach },
@@ -64,8 +106,10 @@
   { "916-obsolete-jit", common_redefine::OnLoad, nullptr },
   { "917-fields-transformation", common_redefine::OnLoad, nullptr },
   { "919-obsolete-fields", common_redefine::OnLoad, nullptr },
-  { "921-hello-failure", common_redefine::OnLoad, nullptr },
+  { "921-hello-failure", common_retransform::OnLoad, nullptr },
   { "926-multi-obsolescence", common_redefine::OnLoad, nullptr },
+  { "930-hello-retransform", common_retransform::OnLoad, nullptr },
+  { "932-transform-saves", common_retransform::OnLoad, nullptr },
 };
 
 static AgentLib* FindAgent(char* name) {
@@ -99,6 +143,28 @@
   RuntimeIsJVM = strncmp(options, "jvm", 3) == 0;
 }
 
+static bool BindFunctionsAttached(JavaVM* vm, const char* class_name) {
+  // Get a JNIEnv. As the thread is attached, we must not destroy it.
+  JNIEnv* env;
+  if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != 0) {
+    printf("Unable to get JNI env!\n");
+    return false;
+  }
+
+  jvmtiEnv* jenv;
+  if (vm->GetEnv(reinterpret_cast<void**>(&jenv), JVMTI_VERSION_1_0) != 0) {
+    printf("Unable to get jvmti env!\n");
+    return false;
+  }
+  SetAllCapabilities(jenv);
+
+  BindFunctions(jenv, env, class_name);
+
+  return true;
+}
+
+}  // namespace
+
 extern "C" JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM* vm, char* options, void* reserved) {
   char* remaining_options = nullptr;
   char* name_option = nullptr;
@@ -109,6 +175,10 @@
 
   SetIsJVM(remaining_options);
 
+  if (!InstallBindCallback(vm)) {
+    return 1;
+  }
+
   AgentLib* lib = FindAgent(name_option);
   OnLoad fn = nullptr;
   if (lib == nullptr) {
@@ -130,6 +200,9 @@
     printf("Unable to find agent name in options: %s\n", options);
     return -1;
   }
+
+  BindFunctionsAttached(vm, "Main");
+
   AgentLib* lib = FindAgent(name_option);
   if (lib == nullptr) {
     printf("Unable to find agent named: %s, add it to the list in test/ti-agent/common_load.cc\n",
diff --git a/test/ti-agent/common_load.h b/test/ti-agent/common_load.h
index fac94b4..d254421 100644
--- a/test/ti-agent/common_load.h
+++ b/test/ti-agent/common_load.h
@@ -17,6 +17,7 @@
 #ifndef ART_TEST_TI_AGENT_COMMON_LOAD_H_
 #define ART_TEST_TI_AGENT_COMMON_LOAD_H_
 
+#include "jni.h"
 #include "openjdkjvmti/jvmti.h"
 
 namespace art {
diff --git a/test/valgrind-suppressions.txt b/test/valgrind-suppressions.txt
index fd3c331..c775f98 100644
--- a/test/valgrind-suppressions.txt
+++ b/test/valgrind-suppressions.txt
@@ -22,3 +22,50 @@
    ...
    fun:_ZN3art7Runtime17InitNativeMethodsEv
 }
+
+# SigQuit runs libbacktrace
+{
+   BackTraceReading64
+   Memcheck:Addr8
+   fun:access_mem_unrestricted
+   fun:_Uelf64_memory_read
+   fun:_Uelf64_valid_object_memory
+   fun:map_create_list
+   fun:unw_map_local_create
+   fun:_ZN14UnwindMapLocal5BuildEv
+   fun:_ZN12BacktraceMap6CreateEib
+}
+{
+   BackTraceReading32
+   Memcheck:Addr4
+   fun:access_mem_unrestricted
+   fun:_Uelf32_memory_read
+   fun:_Uelf32_valid_object_memory
+   fun:map_create_list
+   fun:unw_map_local_create
+   fun:_ZN14UnwindMapLocal5BuildEv
+   fun:_ZN12BacktraceMap6CreateEib
+}
+{
+   BackTraceReading64
+   Memcheck:Addr8
+   fun:access_mem_unrestricted
+   fun:_Uelf64_memory_read
+   fun:_Uelf64_get_load_base
+   fun:map_create_list
+   fun:unw_map_local_create
+   fun:_ZN14UnwindMapLocal5BuildEv
+   fun:_ZN12BacktraceMap6CreateEib
+}
+{
+   BackTraceReading32
+   Memcheck:Addr4
+   fun:access_mem_unrestricted
+   fun:_Uelf32_memory_read
+   fun:_Uelf32_get_load_base
+   fun:map_create_list
+   fun:unw_map_local_create
+   fun:_ZN14UnwindMapLocal5BuildEv
+   fun:_ZN12BacktraceMap6CreateEib
+}
+
diff --git a/tools/dexfuzz/src/dexfuzz/Options.java b/tools/dexfuzz/src/dexfuzz/Options.java
index 99e03e8..af8a05c 100644
--- a/tools/dexfuzz/src/dexfuzz/Options.java
+++ b/tools/dexfuzz/src/dexfuzz/Options.java
@@ -50,6 +50,7 @@
   public static String deviceName = "";
   public static boolean usingSpecificDevice = false;
   public static int repeat = 1;
+  public static int divergenceRetry = 10;
   public static String executeDirectory = "/data/art-test";
   public static String androidRoot = "";
   public static String dumpMutationsFile = "mutations.dump";
@@ -118,6 +119,8 @@
     Log.always("    --repeat=<n>         : Fuzz N programs, executing each one.");
     Log.always("    --short-timeouts     : Shorten timeouts (faster; use if");
     Log.always("                           you want to focus on output divergences)");
+    Log.always("    --divergence-retry=<n> : Number of retries when checking if test is");
+    Log.always("                           self-divergent. (Default: 10)");
     Log.always("  --seed=<seed>          : RNG seed to use");
     Log.always("  --method-mutations=<n> : Maximum number of mutations to perform on each method.");
     Log.always("                           (Default: 3)");
@@ -239,6 +242,8 @@
       maxMethods = Integer.parseInt(value);
     } else if (key.equals("repeat")) {
       repeat = Integer.parseInt(value);
+    } else if (key.equals("divergence-retry")) {
+      divergenceRetry = Integer.parseInt(value);
     } else if (key.equals("log")) {
       Log.setLoggingLevel(LogTag.valueOf(value.toUpperCase()));
     } else if (key.equals("likelihoods")) {
@@ -360,6 +365,10 @@
       Log.error("--repeat must be at least 1!");
       return false;
     }
+    if (divergenceRetry < 0) {
+      Log.error("--divergence-retry cannot be negative!");
+      return false;
+    }
     if (usingProvidedSeed && repeat > 1) {
       Log.error("Cannot use --repeat with --seed");
       return false;
diff --git a/tools/dexfuzz/src/dexfuzz/fuzzers/Fuzzer.java b/tools/dexfuzz/src/dexfuzz/fuzzers/Fuzzer.java
index 1797d90..ccc426c 100644
--- a/tools/dexfuzz/src/dexfuzz/fuzzers/Fuzzer.java
+++ b/tools/dexfuzz/src/dexfuzz/fuzzers/Fuzzer.java
@@ -298,13 +298,13 @@
   }
 
   private boolean checkGoldenExecutorForSelfDivergence(String programName) {
-    // Run golden executor 5 times, make sure it always produces
+    // Run golden executor multiple times, make sure it always produces
     // the same output, otherwise report that it is self-divergent.
 
     // TODO: Instead, produce a list of acceptable outputs, and see if the divergent
     // outputs of the backends fall within this set of outputs.
     String seenOutput = null;
-    for (int i = 0; i < 5; i++) {
+    for (int i = 0; i < Options.divergenceRetry + 1; i++) {
       goldenExecutor.reset();
       goldenExecutor.execute(programName);
       String output = goldenExecutor.getResult().getFlattenedOutput();
diff --git a/tools/stream-trace-converter.py b/tools/stream-trace-converter.py
index 951b05b..7e341f2 100755
--- a/tools/stream-trace-converter.py
+++ b/tools/stream-trace-converter.py
@@ -124,12 +124,20 @@
     self._threads.append('%d\t%s\n' % (tid, str))
     print 'New thread: %d/%s' % (tid, str)
 
+  def ProcessTraceSummary(self, input):
+    summaryLength = ReadIntLE(input)
+    str = input.read(summaryLength)
+    self._summary = str
+    print 'Summary: \"%s\"' % str
+
   def ProcessSpecial(self, input):
     code = ord(input.read(1))
     if code == 1:
       self.ProcessMethod(input)
     elif code == 2:
       self.ProcessThread(input)
+    elif code == 3:
+      self.ProcessTraceSummary(input)
     else:
       raise MyException("Unknown special!")
 
@@ -147,9 +155,24 @@
       print 'Buffer underrun, file was probably truncated. Results should still be usable.'
 
   def Finalize(self, header):
-    header.write('*threads\n')
-    for t in self._threads:
-      header.write(t)
+    # If the summary is present in the input file, use it as the header except
+    # for the methods section which is emtpy in the input file. If not present,
+    # apppend header with the threads that are recorded in the input stream.
+    if (self._summary):
+      # Erase the contents that's already written earlier by PrintHeader.
+      header.seek(0)
+      header.truncate()
+      # Copy the lines from the input summary to the output header until
+      # the methods section is seen.
+      for line in self._summary.splitlines(True):
+        if line == "*methods\n":
+          break
+        else:
+          header.write(line)
+    else:
+      header.write('*threads\n')
+      for t in self._threads:
+        header.write(t)
     header.write('*methods\n')
     for m in self._methods:
       header.write(m)
@@ -166,6 +189,7 @@
 
     self._methods = []
     self._threads = []
+    self._summary = None
     self.Process(input, body)
 
     self.Finalize(header)