Merge "Add a conditional boolean to ScopedAssertNoThreadSuspension."
diff --git a/Android.bp b/Android.bp
index d0e22fb..0ce7916 100644
--- a/Android.bp
+++ b/Android.bp
@@ -31,10 +31,13 @@
     "disassembler",
     "imgdiag",
     "oatdump",
+    "openjdkjvm",
+    "openjdkjvmti",
     "patchoat",
     "profman",
     "runtime",
     "sigchainlib",
+    "simulator",
     "test",
     "tools/cpp-define-generator",
     "tools/dmtracedump",
diff --git a/build/Android.cpplint.mk b/build/Android.cpplint.mk
index 66ac897..2688c04 100644
--- a/build/Android.cpplint.mk
+++ b/build/Android.cpplint.mk
@@ -22,7 +22,7 @@
 ART_CPPLINT_QUIET := --quiet
 ART_CPPLINT_INGORED := \
     runtime/elf.h \
-    runtime/openjdkjvmti/include/jvmti.h
+    openjdkjvmti/include/jvmti.h
 
 # This:
 #  1) Gets a list of all .h & .cc files in the art directory.
diff --git a/compiler/Android.bp b/compiler/Android.bp
index b721d21..f11d256 100644
--- a/compiler/Android.bp
+++ b/compiler/Android.bp
@@ -423,8 +423,11 @@
         },
     },
 
+    header_libs: ["libart_simulator_headers"],
+
     shared_libs: [
         "libartd-compiler",
+        "libartd-simulator-container",
         "libvixld-arm",
         "libvixld-arm64",
 
diff --git a/compiler/dex/dex_to_dex_decompiler_test.cc b/compiler/dex/dex_to_dex_decompiler_test.cc
index 7b56f3e..1ef3ba7 100644
--- a/compiler/dex/dex_to_dex_decompiler_test.cc
+++ b/compiler/dex/dex_to_dex_decompiler_test.cc
@@ -29,6 +29,7 @@
 #include "scoped_thread_state_change-inl.h"
 #include "thread.h"
 #include "verifier/method_verifier-inl.h"
+#include "verifier/verifier_deps.h"
 
 namespace art {
 
@@ -39,6 +40,11 @@
     TimingLogger::ScopedTiming t(__FUNCTION__, &timings);
     compiler_options_->boot_image_ = false;
     compiler_options_->SetCompilerFilter(CompilerFilter::kQuicken);
+    // Create the main VerifierDeps, here instead of in the compiler since we want to aggregate
+    // the results for all the dex files, not just the results for the current dex file.
+    Runtime::Current()->GetCompilerCallbacks()->SetVerifierDeps(
+        new verifier::VerifierDeps(GetDexFiles(class_loader)));
+    compiler_driver_->SetDexFilesForOatFile(GetDexFiles(class_loader));
     compiler_driver_->CompileAll(class_loader, GetDexFiles(class_loader), &timings);
   }
 
diff --git a/compiler/dex/verification_results.cc b/compiler/dex/verification_results.cc
index e657e3b..cfb56e3 100644
--- a/compiler/dex/verification_results.cc
+++ b/compiler/dex/verification_results.cc
@@ -46,10 +46,6 @@
 
 void VerificationResults::ProcessVerifiedMethod(verifier::MethodVerifier* method_verifier) {
   DCHECK(method_verifier != nullptr);
-  if (!compiler_options_->IsAnyCompilationEnabled()) {
-    // Verified methods are only required for quickening and compilation.
-    return;
-  }
   MethodReference ref = method_verifier->GetMethodReference();
   std::unique_ptr<const VerifiedMethod> verified_method(VerifiedMethod::Create(method_verifier));
   if (verified_method == nullptr) {
@@ -104,7 +100,6 @@
 
 const VerifiedMethod* VerificationResults::GetVerifiedMethod(MethodReference ref) {
   const VerifiedMethod* ret = nullptr;
-  DCHECK(compiler_options_->IsAnyCompilationEnabled());
   if (atomic_verified_methods_.Get(DexFileReference(ref.dex_file, ref.dex_method_index), &ret)) {
     return ret;
   }
diff --git a/compiler/driver/compiler_driver.cc b/compiler/driver/compiler_driver.cc
index ed36e11..6e087c5 100644
--- a/compiler/driver/compiler_driver.cc
+++ b/compiler/driver/compiler_driver.cc
@@ -303,7 +303,6 @@
       timings_logger_(timer),
       compiler_context_(nullptr),
       support_boot_image_fixup_(true),
-      dex_files_for_oat_file_(nullptr),
       compiled_method_storage_(swap_fd),
       profile_compilation_info_(profile_compilation_info),
       max_arena_alloc_(0),
@@ -924,8 +923,11 @@
   VLOG(compiler) << "Verify: " << GetMemoryUsageString(false);
 
   if (had_hard_verifier_failure_ && GetCompilerOptions().AbortOnHardVerifierFailure()) {
-    LOG(FATAL) << "Had a hard failure verifying all classes, and was asked to abort in such "
-               << "situations. Please check the log.";
+    // Avoid dumping threads. Even if we shut down the thread pools, there will still be three
+    // instances of this thread's stack.
+    LOG(FATAL_WITHOUT_ABORT) << "Had a hard failure verifying all classes, and was asked to abort "
+                             << "in such situations. Please check the log.";
+    abort();
   }
 
   if (compiler_options_->IsAnyCompilationEnabled()) {
@@ -1912,8 +1914,8 @@
                                 TimingLogger* timings) {
   verifier::VerifierDeps* verifier_deps =
       Runtime::Current()->GetCompilerCallbacks()->GetVerifierDeps();
-  // If there is an existing `VerifierDeps`, try to use it for fast verification.
-  if (verifier_deps == nullptr) {
+  // If there exist VerifierDeps that aren't the ones we just created to output, use them to verify.
+  if (verifier_deps == nullptr || verifier_deps->OutputOnly()) {
     return false;
   }
   TimingLogger::ScopedTiming t("Fast Verify", timings);
@@ -1933,14 +1935,12 @@
   // time. So instead we assume these classes still need to be verified at
   // runtime.
   for (const DexFile* dex_file : dex_files) {
-    // Fetch the list of unverified classes and turn it into a set for faster
-    // lookups.
-    const std::vector<dex::TypeIndex>& unverified_classes =
+    // Fetch the list of unverified classes.
+    const std::set<dex::TypeIndex>& unverified_classes =
         verifier_deps->GetUnverifiedClasses(*dex_file);
-    std::set<dex::TypeIndex> set(unverified_classes.begin(), unverified_classes.end());
     for (uint32_t i = 0; i < dex_file->NumClassDefs(); ++i) {
       const DexFile::ClassDef& class_def = dex_file->GetClassDef(i);
-      if (set.find(class_def.class_idx_) == set.end()) {
+      if (unverified_classes.find(class_def.class_idx_) == unverified_classes.end()) {
         if (compiler_only_verifies) {
           // Just update the compiled_classes_ map. The compiler doesn't need to resolve
           // the type.
@@ -1980,13 +1980,6 @@
 void CompilerDriver::Verify(jobject jclass_loader,
                             const std::vector<const DexFile*>& dex_files,
                             TimingLogger* timings) {
-  // Always add the dex files to compiled_classes_. This happens for all compiler filters.
-  for (const DexFile* dex_file : dex_files) {
-    if (!compiled_classes_.HaveDexFile(dex_file)) {
-      compiled_classes_.AddDexFile(dex_file, dex_file->NumClassDefs());
-    }
-  }
-
   if (FastVerify(jclass_loader, dex_files, timings)) {
     return;
   }
@@ -1996,14 +1989,16 @@
   // non boot image compilation. The verifier will need it to record the new dependencies.
   // Then dex2oat can update the vdex file with these new dependencies.
   if (!GetCompilerOptions().IsBootImage()) {
+    // Dex2oat creates the verifier deps.
     // Create the main VerifierDeps, and set it to this thread.
-    verifier::VerifierDeps* verifier_deps = new verifier::VerifierDeps(dex_files);
-    Runtime::Current()->GetCompilerCallbacks()->SetVerifierDeps(verifier_deps);
+    verifier::VerifierDeps* verifier_deps =
+        Runtime::Current()->GetCompilerCallbacks()->GetVerifierDeps();
+    CHECK(verifier_deps != nullptr);
     Thread::Current()->SetVerifierDeps(verifier_deps);
     // Create per-thread VerifierDeps to avoid contention on the main one.
     // We will merge them after verification.
     for (ThreadPoolWorker* worker : parallel_thread_pool_->GetWorkers()) {
-      worker->GetThread()->SetVerifierDeps(new verifier::VerifierDeps(dex_files));
+      worker->GetThread()->SetVerifierDeps(new verifier::VerifierDeps(dex_files_for_oat_file_));
     }
   }
 
@@ -2028,7 +2023,7 @@
     for (ThreadPoolWorker* worker : parallel_thread_pool_->GetWorkers()) {
       verifier::VerifierDeps* thread_deps = worker->GetThread()->GetVerifierDeps();
       worker->GetThread()->SetVerifierDeps(nullptr);
-      verifier_deps->MergeWith(*thread_deps, dex_files);;
+      verifier_deps->MergeWith(*thread_deps, dex_files_for_oat_file_);
       delete thread_deps;
     }
     Thread::Current()->SetVerifierDeps(nullptr);
@@ -2332,7 +2327,7 @@
             // checks in Thread::AssertThreadSuspensionIsAllowable.
             Runtime* const runtime = Runtime::Current();
             // Run the class initializer in transaction mode.
-            runtime->EnterTransactionMode(klass.Get());
+            runtime->EnterTransactionMode(is_app_image, klass.Get());
             bool success = manager_->GetClassLinker()->EnsureInitialized(soa.Self(), klass, true,
                                                                          true);
             // TODO we detach transaction from runtime to indicate we quit the transactional
@@ -2359,7 +2354,7 @@
                   *file_log << exception->Dump() << "\n";
                 }
                 soa.Self()->ClearException();
-                runtime->RollbackAndExitTransactionMode();
+                runtime->RollbackAllTransactions();
                 CHECK_EQ(old_status, klass->GetStatus()) << "Previous class status not restored";
               } else if (is_boot_image) {
                 // For boot image, we want to put the updated status in the oat class since we can't
@@ -2452,7 +2447,8 @@
 
   bool ResolveTypesOfMethods(Thread* self, ArtMethod* m)
       REQUIRES_SHARED(Locks::mutator_lock_) {
-    auto rtn_type = m->GetReturnType(true);  // return value is discarded because resolve will be done internally.
+    // Return value of ResolveReturnType() is discarded because resolve will be done internally.
+    ObjPtr<mirror::Class> rtn_type = m->ResolveReturnType();
     if (rtn_type == nullptr) {
       self->ClearException();
       return false;
@@ -2461,7 +2457,7 @@
     if (types != nullptr) {
       for (uint32_t i = 0; i < types->Size(); ++i) {
         dex::TypeIndex param_type_idx = types->GetTypeItem(i).type_idx_;
-        auto param_type = m->GetClassFromTypeIndex(param_type_idx, true);
+        ObjPtr<mirror::Class> param_type = m->ResolveClassFromTypeIndex(param_type_idx);
         if (param_type == nullptr) {
           self->ClearException();
           return false;
@@ -2698,7 +2694,14 @@
             : profile_compilation_info_->DumpInfo(&dex_files));
   }
 
-  DCHECK(current_dex_to_dex_methods_ == nullptr);
+  current_dex_to_dex_methods_ = nullptr;
+  Thread* const self = Thread::Current();
+  {
+    // Clear in case we aren't the first call to Compile.
+    MutexLock mu(self, dex_to_dex_references_lock_);
+    dex_to_dex_references_.clear();
+  }
+
   for (const DexFile* dex_file : dex_files) {
     CHECK(dex_file != nullptr);
     CompileDexFile(class_loader,
@@ -2717,7 +2720,7 @@
   {
     // From this point on, we shall not modify dex_to_dex_references_, so
     // just grab a reference to it that we use without holding the mutex.
-    MutexLock lock(Thread::Current(), dex_to_dex_references_lock_);
+    MutexLock lock(self, dex_to_dex_references_lock_);
     dex_to_dex_references = ArrayRef<DexFileMethodSet>(dex_to_dex_references_);
   }
   for (const auto& method_set : dex_to_dex_references) {
@@ -2876,9 +2879,9 @@
 bool CompilerDriver::GetCompiledClass(ClassReference ref, mirror::Class::Status* status) const {
   DCHECK(status != nullptr);
   // The table doesn't know if something wasn't inserted. For this case it will return
-  // kStatusNotReady. To handle this, just assume anything not verified is not compiled.
+  // kStatusNotReady. To handle this, just assume anything we didn't try to verify is not compiled.
   if (!compiled_classes_.Get(DexFileReference(ref.first, ref.second), status) ||
-      *status < mirror::Class::kStatusVerified) {
+      *status < mirror::Class::kStatusRetryVerificationAtRuntime) {
     return false;
   }
   return true;
@@ -2910,7 +2913,7 @@
       if (kIsDebugBuild) {
         // Check to make sure it's not a dex file for an oat file we are compiling since these
         // should always succeed. These do not include classes in for used libraries.
-        for (const DexFile* dex_file : *dex_files_for_oat_file_) {
+        for (const DexFile* dex_file : GetDexFilesForOatFile()) {
           CHECK_NE(dex_ref.dex_file, dex_file) << dex_ref.dex_file->GetLocation();
         }
       }
@@ -3028,4 +3031,13 @@
   single_thread_pool_.reset();
 }
 
+void CompilerDriver::SetDexFilesForOatFile(const std::vector<const DexFile*>& dex_files) {
+  dex_files_for_oat_file_ = dex_files;
+  for (const DexFile* dex_file : dex_files) {
+    if (!compiled_classes_.HaveDexFile(dex_file)) {
+      compiled_classes_.AddDexFile(dex_file, dex_file->NumClassDefs());
+    }
+  }
+}
+
 }  // namespace art
diff --git a/compiler/driver/compiler_driver.h b/compiler/driver/compiler_driver.h
index ecaed83..d9886a2 100644
--- a/compiler/driver/compiler_driver.h
+++ b/compiler/driver/compiler_driver.h
@@ -103,15 +103,11 @@
   ~CompilerDriver();
 
   // Set dex files that will be stored in the oat file after being compiled.
-  void SetDexFilesForOatFile(const std::vector<const DexFile*>& dex_files) {
-    dex_files_for_oat_file_ = &dex_files;
-  }
+  void SetDexFilesForOatFile(const std::vector<const DexFile*>& dex_files);
 
   // Get dex file that will be stored in the oat file after being compiled.
   ArrayRef<const DexFile* const> GetDexFilesForOatFile() const {
-    return (dex_files_for_oat_file_ != nullptr)
-        ? ArrayRef<const DexFile* const>(*dex_files_for_oat_file_)
-        : ArrayRef<const DexFile* const>();
+    return ArrayRef<const DexFile* const>(dex_files_for_oat_file_);
   }
 
   void CompileAll(jobject class_loader,
@@ -532,7 +528,7 @@
   bool support_boot_image_fixup_;
 
   // List of dex files that will be stored in the oat file.
-  const std::vector<const DexFile*>* dex_files_for_oat_file_;
+  std::vector<const DexFile*> dex_files_for_oat_file_;
 
   CompiledMethodStorage compiled_method_storage_;
 
diff --git a/compiler/driver/compiler_driver_test.cc b/compiler/driver/compiler_driver_test.cc
index 10bfd97..4da3e0d 100644
--- a/compiler/driver/compiler_driver_test.cc
+++ b/compiler/driver/compiler_driver_test.cc
@@ -42,7 +42,9 @@
   void CompileAll(jobject class_loader) REQUIRES(!Locks::mutator_lock_) {
     TimingLogger timings("CompilerDriverTest::CompileAll", false, false);
     TimingLogger::ScopedTiming t(__FUNCTION__, &timings);
-    compiler_driver_->CompileAll(class_loader, GetDexFiles(class_loader), &timings);
+    dex_files_ = GetDexFiles(class_loader);
+    compiler_driver_->SetDexFilesForOatFile(dex_files_);;
+    compiler_driver_->CompileAll(class_loader, dex_files_, &timings);
     t.NewTiming("MakeAllExecutable");
     MakeAllExecutable(class_loader);
   }
@@ -95,6 +97,7 @@
   JNIEnv* env_;
   jclass class_;
   jmethodID mid_;
+  std::vector<const DexFile*> dex_files_;
 };
 
 // Disabled due to 10 second runtime on host
@@ -363,6 +366,40 @@
   CheckVerifiedClass(class_loader, "LSecond;");
 }
 
+// Test that a class of status kStatusRetryVerificationAtRuntime is indeed recorded that way in the
+// driver.
+TEST_F(CompilerDriverVerifyTest, RetryVerifcationStatus) {
+  Thread* const self = Thread::Current();
+  jobject class_loader;
+  std::vector<const DexFile*> dex_files;
+  const DexFile* dex_file = nullptr;
+  {
+    ScopedObjectAccess soa(self);
+    class_loader = LoadDex("ProfileTestMultiDex");
+    ASSERT_NE(class_loader, nullptr);
+    dex_files = GetDexFiles(class_loader);
+    ASSERT_GT(dex_files.size(), 0u);
+    dex_file = dex_files.front();
+  }
+  compiler_driver_->SetDexFilesForOatFile(dex_files);
+  ClassReference ref(dex_file, 0u);
+  // Test that the status is read from the compiler driver as expected.
+  for (size_t i = mirror::Class::kStatusRetryVerificationAtRuntime;
+      i < mirror::Class::kStatusMax;
+      ++i) {
+    const mirror::Class::Status expected_status = static_cast<mirror::Class::Status>(i);
+    // Skip unsupported status that are not supposed to be ever recorded.
+    if (expected_status == mirror::Class::kStatusVerifyingAtRuntime ||
+        expected_status == mirror::Class::kStatusInitializing) {
+      continue;
+    }
+    compiler_driver_->RecordClassStatus(ref, expected_status);
+    mirror::Class::Status status = {};
+    ASSERT_TRUE(compiler_driver_->GetCompiledClass(ref, &status));
+    EXPECT_EQ(status, expected_status);
+  }
+}
+
 // TODO: need check-cast test (when stub complete & we can throw/catch
 
 }  // namespace art
diff --git a/compiler/driver/compiler_options.cc b/compiler/driver/compiler_options.cc
index 76f0ae9..3cacc2c 100644
--- a/compiler/driver/compiler_options.cc
+++ b/compiler/driver/compiler_options.cc
@@ -144,6 +144,8 @@
     ParseDouble(option.data(), '=', 0.0, 100.0, &top_k_profile_threshold_, Usage);
   } else if (option == "--abort-on-hard-verifier-error") {
     abort_on_hard_verifier_failure_ = true;
+  } else if (option == "--no-abort-on-hard-verifier-error") {
+    abort_on_hard_verifier_failure_ = false;
   } else if (option.starts_with("--dump-init-failures=")) {
     ParseDumpInitFailures(option, Usage);
   } else if (option.starts_with("--dump-cfg=")) {
diff --git a/compiler/image_test.h b/compiler/image_test.h
index 57d0987..daa4b11 100644
--- a/compiler/image_test.h
+++ b/compiler/image_test.h
@@ -214,7 +214,8 @@
                                                       /*compile_app_image*/false,
                                                       storage_mode,
                                                       oat_filename_vector,
-                                                      dex_file_to_oat_index_map));
+                                                      dex_file_to_oat_index_map,
+                                                      /*dirty_image_objects*/nullptr));
   {
     {
       jobject class_loader = nullptr;
diff --git a/compiler/image_writer.cc b/compiler/image_writer.cc
index 318009c..9e4971c 100644
--- a/compiler/image_writer.cc
+++ b/compiler/image_writer.cc
@@ -579,7 +579,12 @@
         }
       }
 
-      if (klass->GetStatus() == Class::kStatusInitialized) {
+      // Move known dirty objects into their own sections. This includes:
+      //   - classes with dirty static fields.
+      if (dirty_image_objects_ != nullptr &&
+          dirty_image_objects_->find(klass->PrettyDescriptor()) != dirty_image_objects_->end()) {
+        bin = kBinKnownDirty;
+      } else if (klass->GetStatus() == Class::kStatusInitialized) {
         bin = kBinClassInitialized;
 
         // If the class's static fields are all final, put it into a separate bin
@@ -770,18 +775,18 @@
       *result_ = true;
     }
 
-    // Record the object visited in case of circular reference.
-    visited_->emplace(ref);
     if (ref->IsClass()) {
       *result_ = *result_ ||
           image_writer_->PruneAppImageClassInternal(ref->AsClass(), early_exit_, visited_);
     } else {
+      // Record the object visited in case of circular reference.
+      visited_->emplace(ref);
       *result_ = *result_ ||
           image_writer_->PruneAppImageClassInternal(klass, early_exit_, visited_);
       ref->VisitReferences(*this, *this);
+      // Clean up before exit for next call of this function.
+      visited_->erase(ref);
     }
-    // Clean up before exit for next call of this function.
-    visited_->erase(ref);
   }
 
   ALWAYS_INLINE void operator() (ObjPtr<mirror::Class> klass ATTRIBUTE_UNUSED,
@@ -894,7 +899,7 @@
                                                 &my_early_exit,
                                                 visited);
   // Remove the class if the dex file is not in the set of dex files. This happens for classes that
-  // are from uses library if there is no profile. b/30688277
+  // are from uses-library if there is no profile. b/30688277
   mirror::DexCache* dex_cache = klass->GetDexCache();
   if (dex_cache != nullptr) {
     result = result ||
@@ -1153,9 +1158,22 @@
   Thread* self = Thread::Current();
   ScopedAssertNoThreadSuspension sa(__FUNCTION__);
 
-  // Clear class table strong roots so that dex caches can get pruned. We require pruning the class
-  // path dex caches.
-  class_linker->ClearClassTableStrongRoots();
+  // Prune uses-library dex caches. Only prune the uses-library dex caches since we want to make
+  // sure the other ones don't get unloaded before the OatWriter runs.
+  class_linker->VisitClassTables(
+      [&](ClassTable* table) REQUIRES_SHARED(Locks::mutator_lock_) {
+    table->RemoveStrongRoots(
+        [&](GcRoot<mirror::Object> root) REQUIRES_SHARED(Locks::mutator_lock_) {
+      ObjPtr<mirror::Object> obj = root.Read();
+      if (obj->IsDexCache()) {
+        // Return true if the dex file is not one of the ones in the map.
+        return dex_file_oat_index_map_.find(obj->AsDexCache()->GetDexFile()) ==
+            dex_file_oat_index_map_.end();
+      }
+      // Return false to avoid removing.
+      return false;
+    });
+  });
 
   // Remove the undesired classes from the class roots.
   ObjPtr<mirror::ClassLoader> class_loader;
@@ -2761,7 +2779,8 @@
     bool compile_app_image,
     ImageHeader::StorageMode image_storage_mode,
     const std::vector<const char*>& oat_filenames,
-    const std::unordered_map<const DexFile*, size_t>& dex_file_oat_index_map)
+    const std::unordered_map<const DexFile*, size_t>& dex_file_oat_index_map,
+    const std::unordered_set<std::string>* dirty_image_objects)
     : compiler_driver_(compiler_driver),
       global_image_begin_(reinterpret_cast<uint8_t*>(image_begin)),
       image_objects_offset_begin_(0),
@@ -2773,7 +2792,8 @@
       clean_methods_(0u),
       image_storage_mode_(image_storage_mode),
       oat_filenames_(oat_filenames),
-      dex_file_oat_index_map_(dex_file_oat_index_map) {
+      dex_file_oat_index_map_(dex_file_oat_index_map),
+      dirty_image_objects_(dirty_image_objects) {
   CHECK_NE(image_begin, 0U);
   std::fill_n(image_methods_, arraysize(image_methods_), nullptr);
   CHECK_EQ(compile_app_image, !Runtime::Current()->GetHeap()->GetBootImageSpaces().empty())
diff --git a/compiler/image_writer.h b/compiler/image_writer.h
index 34bbbad..866e204 100644
--- a/compiler/image_writer.h
+++ b/compiler/image_writer.h
@@ -75,7 +75,8 @@
               bool compile_app_image,
               ImageHeader::StorageMode image_storage_mode,
               const std::vector<const char*>& oat_filenames,
-              const std::unordered_map<const DexFile*, size_t>& dex_file_oat_index_map);
+              const std::unordered_map<const DexFile*, size_t>& dex_file_oat_index_map,
+              const std::unordered_set<std::string>* dirty_image_objects);
 
   bool PrepareImageAddressSpace();
 
@@ -159,6 +160,7 @@
   // Classify different kinds of bins that objects end up getting packed into during image writing.
   // Ordered from dirtiest to cleanest (until ArtMethods).
   enum Bin {
+    kBinKnownDirty,               // Known dirty objects from --dirty-image-objects list
     kBinMiscDirty,                // Dex caches, object locks, etc...
     kBinClassVerified,            // Class verified, but initializers haven't been run
     // Unknown mix of clean/dirty:
@@ -599,6 +601,9 @@
   // Map of dex files to the indexes of oat files that they were compiled into.
   const std::unordered_map<const DexFile*, size_t>& dex_file_oat_index_map_;
 
+  // Set of objects known to be dirty in the image. Can be nullptr if there are none.
+  const std::unordered_set<std::string>* dirty_image_objects_;
+
   class ComputeLazyFieldsForClassesVisitor;
   class FixupClassVisitor;
   class FixupRootVisitor;
diff --git a/compiler/oat_writer.cc b/compiler/oat_writer.cc
index 4d258af..d7e3a28 100644
--- a/compiler/oat_writer.cc
+++ b/compiler/oat_writer.cc
@@ -1282,9 +1282,12 @@
   bool StartClass(const DexFile* dex_file, size_t class_def_index) OVERRIDE
       REQUIRES_SHARED(Locks::mutator_lock_) {
     OatDexMethodVisitor::StartClass(dex_file, class_def_index);
-    if (dex_cache_ == nullptr || dex_cache_->GetDexFile() != dex_file) {
-      dex_cache_ = class_linker_->FindDexCache(Thread::Current(), *dex_file);
-      DCHECK(dex_cache_ != nullptr);
+    if (writer_->GetCompilerDriver()->GetCompilerOptions().IsAotCompilationEnabled()) {
+      // Only need to set the dex cache if we have compilation. Other modes might have unloaded it.
+      if (dex_cache_ == nullptr || dex_cache_->GetDexFile() != dex_file) {
+        dex_cache_ = class_linker_->FindDexCache(Thread::Current(), *dex_file);
+        DCHECK(dex_cache_ != nullptr);
+      }
     }
     return true;
   }
diff --git a/compiler/optimizing/code_generator_vector_arm64.cc b/compiler/optimizing/code_generator_vector_arm64.cc
index f422b9f..9095ecd 100644
--- a/compiler/optimizing/code_generator_vector_arm64.cc
+++ b/compiler/optimizing/code_generator_vector_arm64.cc
@@ -15,7 +15,9 @@
  */
 
 #include "code_generator_arm64.h"
+
 #include "mirror/array-inl.h"
+#include "mirror/string.h"
 
 using namespace vixl::aarch64;  // NOLINT(build/namespaces)
 
diff --git a/compiler/optimizing/code_generator_vector_x86.cc b/compiler/optimizing/code_generator_vector_x86.cc
index 14782d7..e7aec76 100644
--- a/compiler/optimizing/code_generator_vector_x86.cc
+++ b/compiler/optimizing/code_generator_vector_x86.cc
@@ -15,7 +15,9 @@
  */
 
 #include "code_generator_x86.h"
+
 #include "mirror/array-inl.h"
+#include "mirror/string.h"
 
 namespace art {
 namespace x86 {
diff --git a/compiler/optimizing/code_generator_vector_x86_64.cc b/compiler/optimizing/code_generator_vector_x86_64.cc
index 246044e..c7ee81c 100644
--- a/compiler/optimizing/code_generator_vector_x86_64.cc
+++ b/compiler/optimizing/code_generator_vector_x86_64.cc
@@ -15,7 +15,9 @@
  */
 
 #include "code_generator_x86_64.h"
+
 #include "mirror/array-inl.h"
+#include "mirror/string.h"
 
 namespace art {
 namespace x86_64 {
diff --git a/compiler/optimizing/codegen_test_utils.h b/compiler/optimizing/codegen_test_utils.h
index 1b38acd..cada2e6 100644
--- a/compiler/optimizing/codegen_test_utils.h
+++ b/compiler/optimizing/codegen_test_utils.h
@@ -28,6 +28,7 @@
 #include "arch/x86/instruction_set_features_x86.h"
 #include "arch/x86/registers_x86.h"
 #include "arch/x86_64/instruction_set_features_x86_64.h"
+#include "code_simulator.h"
 #include "code_simulator_container.h"
 #include "common_compiler_test.h"
 #include "graph_checker.h"
diff --git a/compiler/optimizing/inliner.cc b/compiler/optimizing/inliner.cc
index 38a1a8c..0141c26 100644
--- a/compiler/optimizing/inliner.cc
+++ b/compiler/optimizing/inliner.cc
@@ -1948,7 +1948,7 @@
           declared_rti.IsStrictSupertypeOf(actual_rti);
 }
 
-ReferenceTypeInfo HInliner::GetClassRTI(mirror::Class* klass) {
+ReferenceTypeInfo HInliner::GetClassRTI(ObjPtr<mirror::Class> klass) {
   return ReferenceTypePropagation::IsAdmissible(klass)
       ? ReferenceTypeInfo::Create(handles_->NewHandle(klass))
       : graph_->GetInexactObjectRti();
@@ -1976,9 +1976,8 @@
        ++param_idx, ++input_idx) {
     HInstruction* input = invoke_instruction->InputAt(input_idx);
     if (input->GetType() == Primitive::kPrimNot) {
-      mirror::Class* param_cls = resolved_method->GetClassFromTypeIndex(
-          param_list->GetTypeItem(param_idx).type_idx_,
-          /* resolve */ false);
+      ObjPtr<mirror::Class> param_cls = resolved_method->LookupResolvedClassFromTypeIndex(
+          param_list->GetTypeItem(param_idx).type_idx_);
       if (IsReferenceTypeRefinement(GetClassRTI(param_cls),
                                     /* declared_can_be_null */ true,
                                     input)) {
@@ -2027,7 +2026,7 @@
         // TODO: we could be more precise by merging the phi inputs but that requires
         // some functionality from the reference type propagation.
         DCHECK(return_replacement->IsPhi());
-        mirror::Class* cls = resolved_method->GetReturnType(false /* resolve */);
+        ObjPtr<mirror::Class> cls = resolved_method->LookupResolvedReturnType();
         return_replacement->SetReferenceTypeInfo(GetClassRTI(cls));
       }
     }
diff --git a/compiler/optimizing/inliner.h b/compiler/optimizing/inliner.h
index 62c6713..c4b3a32 100644
--- a/compiler/optimizing/inliner.h
+++ b/compiler/optimizing/inliner.h
@@ -207,7 +207,7 @@
   // Creates an instance of ReferenceTypeInfo from `klass` if `klass` is
   // admissible (see ReferenceTypePropagation::IsAdmissible for details).
   // Otherwise returns inexact Object RTI.
-  ReferenceTypeInfo GetClassRTI(mirror::Class* klass) REQUIRES_SHARED(Locks::mutator_lock_);
+  ReferenceTypeInfo GetClassRTI(ObjPtr<mirror::Class> klass) REQUIRES_SHARED(Locks::mutator_lock_);
 
   bool ArgumentTypesMoreSpecific(HInvoke* invoke_instruction, ArtMethod* resolved_method)
     REQUIRES_SHARED(Locks::mutator_lock_);
diff --git a/compiler/optimizing/intrinsics_mips.cc b/compiler/optimizing/intrinsics_mips.cc
index 4cea6df..2669d97 100644
--- a/compiler/optimizing/intrinsics_mips.cc
+++ b/compiler/optimizing/intrinsics_mips.cc
@@ -22,6 +22,7 @@
 #include "entrypoints/quick/quick_entrypoints.h"
 #include "intrinsics.h"
 #include "mirror/array-inl.h"
+#include "mirror/object_array-inl.h"
 #include "mirror/string.h"
 #include "scoped_thread_state_change-inl.h"
 #include "thread.h"
diff --git a/compiler/optimizing/intrinsics_mips64.cc b/compiler/optimizing/intrinsics_mips64.cc
index d785567..74be954 100644
--- a/compiler/optimizing/intrinsics_mips64.cc
+++ b/compiler/optimizing/intrinsics_mips64.cc
@@ -22,6 +22,7 @@
 #include "entrypoints/quick/quick_entrypoints.h"
 #include "intrinsics.h"
 #include "mirror/array-inl.h"
+#include "mirror/object_array-inl.h"
 #include "mirror/string.h"
 #include "scoped_thread_state_change-inl.h"
 #include "thread.h"
diff --git a/compiler/optimizing/load_store_analysis.h b/compiler/optimizing/load_store_analysis.h
index 86fb8e0..a2c1794 100644
--- a/compiler/optimizing/load_store_analysis.h
+++ b/compiler/optimizing/load_store_analysis.h
@@ -466,6 +466,11 @@
     CreateReferenceInfoForReferenceType(new_instance);
   }
 
+  void VisitNewArray(HNewArray* new_array) OVERRIDE {
+    // Any references appearing in the ref_info_array_ so far cannot alias with new_array.
+    CreateReferenceInfoForReferenceType(new_array);
+  }
+
   void VisitInvokeStaticOrDirect(HInvokeStaticOrDirect* instruction) OVERRIDE {
     CreateReferenceInfoForReferenceType(instruction);
   }
@@ -478,6 +483,22 @@
     CreateReferenceInfoForReferenceType(instruction);
   }
 
+  void VisitInvokeUnresolved(HInvokeUnresolved* instruction) OVERRIDE {
+    CreateReferenceInfoForReferenceType(instruction);
+  }
+
+  void VisitInvokePolymorphic(HInvokePolymorphic* instruction) OVERRIDE {
+    CreateReferenceInfoForReferenceType(instruction);
+  }
+
+  void VisitLoadString(HLoadString* instruction) OVERRIDE {
+    CreateReferenceInfoForReferenceType(instruction);
+  }
+
+  void VisitPhi(HPhi* instruction) OVERRIDE {
+    CreateReferenceInfoForReferenceType(instruction);
+  }
+
   void VisitParameterValue(HParameterValue* instruction) OVERRIDE {
     CreateReferenceInfoForReferenceType(instruction);
   }
diff --git a/compiler/optimizing/loop_optimization.cc b/compiler/optimizing/loop_optimization.cc
index 274f065..0ef7dcd 100644
--- a/compiler/optimizing/loop_optimization.cc
+++ b/compiler/optimizing/loop_optimization.cc
@@ -40,6 +40,8 @@
   instruction->RemoveAsUserOfAllInputs();
   instruction->RemoveEnvironmentUsers();
   instruction->GetBlock()->RemoveInstructionOrPhi(instruction, /*ensure_safety=*/ false);
+  RemoveEnvironmentUses(instruction);
+  ResetEnvironmentInputRecords(instruction);
 }
 
 // Detect a goto block and sets succ to the single successor.
@@ -267,6 +269,21 @@
   return instruction;
 }
 
+// Check that instructions from the induction sets are fully removed: have no uses
+// and no other instructions use them.
+static bool CheckInductionSetFullyRemoved(ArenaSet<HInstruction*>* iset) {
+  for (HInstruction* instr : *iset) {
+    if (instr->GetBlock() != nullptr ||
+        !instr->GetUses().empty() ||
+        !instr->GetEnvUses().empty() ||
+        HasEnvironmentUsedByOthers(instr)) {
+      return false;
+    }
+  }
+
+  return true;
+}
+
 //
 // Class methods.
 //
@@ -448,6 +465,9 @@
         for (HInstruction* i : *iset_) {
           RemoveFromCycle(i);
         }
+
+        // Check that there are no records of the deleted instructions.
+        DCHECK(CheckInductionSetFullyRemoved(iset_));
         simplified_ = true;
       }
     }
diff --git a/compiler/optimizing/loop_optimization_test.cc b/compiler/optimizing/loop_optimization_test.cc
index 5b93506..b5b03d8 100644
--- a/compiler/optimizing/loop_optimization_test.cc
+++ b/compiler/optimizing/loop_optimization_test.cc
@@ -195,4 +195,44 @@
   EXPECT_EQ("[[[[[[[[[[][][][][][][][][][]]]]]]]]]]", LoopStructure());
 }
 
+// Check that SimplifyLoop() doesn't invalidate data flow when ordering loop headers'
+// predecessors.
+TEST_F(LoopOptimizationTest, SimplifyLoop) {
+  // Can't use AddLoop as we want special order for blocks predecessors.
+  HBasicBlock* header = new (&allocator_) HBasicBlock(graph_);
+  HBasicBlock* body = new (&allocator_) HBasicBlock(graph_);
+  graph_->AddBlock(header);
+  graph_->AddBlock(body);
+
+  // Control flow: make a loop back edge first in the list of predecessors.
+  entry_block_->RemoveSuccessor(return_block_);
+  body->AddSuccessor(header);
+  entry_block_->AddSuccessor(header);
+  header->AddSuccessor(body);
+  header->AddSuccessor(return_block_);
+  DCHECK(header->GetSuccessors()[1] == return_block_);
+
+  // Data flow.
+  header->AddInstruction(new (&allocator_) HIf(parameter_));
+  body->AddInstruction(new (&allocator_) HGoto());
+
+  HPhi* phi = new (&allocator_) HPhi(&allocator_, 0, 0, Primitive::kPrimInt);
+  HInstruction* add = new (&allocator_) HAdd(Primitive::kPrimInt, phi, parameter_);
+  header->AddPhi(phi);
+  body->AddInstruction(add);
+
+  phi->AddInput(add);
+  phi->AddInput(parameter_);
+
+  graph_->ClearLoopInformation();
+  graph_->ClearDominanceInformation();
+  graph_->BuildDominatorTree();
+
+  // Check that after optimizations in BuildDominatorTree()/SimplifyCFG() phi inputs
+  // are still mapped correctly to the block predecessors.
+  for (size_t i = 0, e = phi->InputCount(); i < e; i++) {
+    HInstruction* input = phi->InputAt(i);
+    ASSERT_TRUE(input->GetBlock()->Dominates(header->GetPredecessors()[i]));
+  }
+}
 }  // namespace art
diff --git a/compiler/optimizing/nodes.cc b/compiler/optimizing/nodes.cc
index ca48e08..ddd798b 100644
--- a/compiler/optimizing/nodes.cc
+++ b/compiler/optimizing/nodes.cc
@@ -90,7 +90,8 @@
   }
 }
 
-static void RemoveEnvironmentUses(HInstruction* instruction) {
+// Remove the environment use records of the instruction for users.
+void RemoveEnvironmentUses(HInstruction* instruction) {
   for (HEnvironment* environment = instruction->GetEnvironment();
        environment != nullptr;
        environment = environment->GetParent()) {
@@ -102,6 +103,35 @@
   }
 }
 
+// Return whether the instruction has an environment and it's used by others.
+bool HasEnvironmentUsedByOthers(HInstruction* instruction) {
+  for (HEnvironment* environment = instruction->GetEnvironment();
+       environment != nullptr;
+       environment = environment->GetParent()) {
+    for (size_t i = 0, e = environment->Size(); i < e; ++i) {
+      HInstruction* user = environment->GetInstructionAt(i);
+      if (user != nullptr) {
+        return true;
+      }
+    }
+  }
+  return false;
+}
+
+// Reset environment records of the instruction itself.
+void ResetEnvironmentInputRecords(HInstruction* instruction) {
+  for (HEnvironment* environment = instruction->GetEnvironment();
+       environment != nullptr;
+       environment = environment->GetParent()) {
+    for (size_t i = 0, e = environment->Size(); i < e; ++i) {
+      DCHECK(environment->GetHolder() == instruction);
+      if (environment->GetInstructionAt(i) != nullptr) {
+        environment->SetRawEnvAt(i, nullptr);
+      }
+    }
+  }
+}
+
 static void RemoveAsUser(HInstruction* instruction) {
   instruction->RemoveAsUserOfAllInputs();
   RemoveEnvironmentUses(instruction);
@@ -328,6 +358,35 @@
   }
 }
 
+// Reorder phi inputs to match reordering of the block's predecessors.
+static void FixPhisAfterPredecessorsReodering(HBasicBlock* block, size_t first, size_t second) {
+  for (HInstructionIterator it(block->GetPhis()); !it.Done(); it.Advance()) {
+    HPhi* phi = it.Current()->AsPhi();
+    HInstruction* first_instr = phi->InputAt(first);
+    HInstruction* second_instr = phi->InputAt(second);
+    phi->ReplaceInput(first_instr, second);
+    phi->ReplaceInput(second_instr, first);
+  }
+}
+
+// Make sure that the first predecessor of a loop header is the incoming block.
+void HGraph::OrderLoopHeaderPredecessors(HBasicBlock* header) {
+  DCHECK(header->IsLoopHeader());
+  HLoopInformation* info = header->GetLoopInformation();
+  if (info->IsBackEdge(*header->GetPredecessors()[0])) {
+    HBasicBlock* to_swap = header->GetPredecessors()[0];
+    for (size_t pred = 1, e = header->GetPredecessors().size(); pred < e; ++pred) {
+      HBasicBlock* predecessor = header->GetPredecessors()[pred];
+      if (!info->IsBackEdge(*predecessor)) {
+        header->predecessors_[pred] = to_swap;
+        header->predecessors_[0] = predecessor;
+        FixPhisAfterPredecessorsReodering(header, 0, pred);
+        break;
+      }
+    }
+  }
+}
+
 void HGraph::SimplifyLoop(HBasicBlock* header) {
   HLoopInformation* info = header->GetLoopInformation();
 
@@ -351,18 +410,7 @@
     pre_header->AddSuccessor(header);
   }
 
-  // Make sure the first predecessor of a loop header is the incoming block.
-  if (info->IsBackEdge(*header->GetPredecessors()[0])) {
-    HBasicBlock* to_swap = header->GetPredecessors()[0];
-    for (size_t pred = 1, e = header->GetPredecessors().size(); pred < e; ++pred) {
-      HBasicBlock* predecessor = header->GetPredecessors()[pred];
-      if (!info->IsBackEdge(*predecessor)) {
-        header->predecessors_[pred] = to_swap;
-        header->predecessors_[0] = predecessor;
-        break;
-      }
-    }
-  }
+  OrderLoopHeaderPredecessors(header);
 
   HInstruction* first_instruction = header->GetFirstInstruction();
   if (first_instruction != nullptr && first_instruction->IsSuspendCheck()) {
diff --git a/compiler/optimizing/nodes.h b/compiler/optimizing/nodes.h
index fa29378..488d472 100644
--- a/compiler/optimizing/nodes.h
+++ b/compiler/optimizing/nodes.h
@@ -418,6 +418,7 @@
   HBasicBlock* SplitEdge(HBasicBlock* block, HBasicBlock* successor);
 
   void SplitCriticalEdge(HBasicBlock* block, HBasicBlock* successor);
+  void OrderLoopHeaderPredecessors(HBasicBlock* header);
   void SimplifyLoop(HBasicBlock* header);
 
   int32_t GetNextInstructionId() {
@@ -7059,6 +7060,10 @@
   return instruction;
 }
 
+void RemoveEnvironmentUses(HInstruction* instruction);
+bool HasEnvironmentUsedByOthers(HInstruction* instruction);
+void ResetEnvironmentInputRecords(HInstruction* instruction);
+
 }  // namespace art
 
 #endif  // ART_COMPILER_OPTIMIZING_NODES_H_
diff --git a/compiler/optimizing/prepare_for_register_allocation.cc b/compiler/optimizing/prepare_for_register_allocation.cc
index aa42fd6..7c6b69f 100644
--- a/compiler/optimizing/prepare_for_register_allocation.cc
+++ b/compiler/optimizing/prepare_for_register_allocation.cc
@@ -208,8 +208,8 @@
 
 void PrepareForRegisterAllocation::VisitInvokeStaticOrDirect(HInvokeStaticOrDirect* invoke) {
   if (invoke->IsStaticWithExplicitClinitCheck()) {
-    HLoadClass* last_input = invoke->GetInputs().back()->AsLoadClass();
-    DCHECK(last_input != nullptr)
+    HInstruction* last_input = invoke->GetInputs().back();
+    DCHECK(last_input->IsLoadClass())
         << "Last input is not HLoadClass. It is " << last_input->DebugName();
 
     // Detach the explicit class initialization check from the invoke.
diff --git a/compiler/optimizing/reference_type_propagation.cc b/compiler/optimizing/reference_type_propagation.cc
index f172e16..561c9ea 100644
--- a/compiler/optimizing/reference_type_propagation.cc
+++ b/compiler/optimizing/reference_type_propagation.cc
@@ -848,7 +848,7 @@
 
   ScopedObjectAccess soa(Thread::Current());
   ArtMethod* method = instr->GetResolvedMethod();
-  mirror::Class* klass = (method == nullptr) ? nullptr : method->GetReturnType(/* resolve */ false);
+  ObjPtr<mirror::Class> klass = (method == nullptr) ? nullptr : method->LookupResolvedReturnType();
   SetClassAsTypeInfo(instr, klass, /* is_exact */ false);
 }
 
diff --git a/compiler/optimizing/reference_type_propagation.h b/compiler/optimizing/reference_type_propagation.h
index 215e967..b19f473 100644
--- a/compiler/optimizing/reference_type_propagation.h
+++ b/compiler/optimizing/reference_type_propagation.h
@@ -46,7 +46,7 @@
 
   // Returns true if klass is admissible to the propagation: non-null and resolved.
   // For an array type, we also check if the component type is admissible.
-  static bool IsAdmissible(mirror::Class* klass) REQUIRES_SHARED(Locks::mutator_lock_) {
+  static bool IsAdmissible(ObjPtr<mirror::Class> klass) REQUIRES_SHARED(Locks::mutator_lock_) {
     return klass != nullptr &&
            klass->IsResolved() &&
            (!klass->IsArrayClass() || IsAdmissible(klass->GetComponentType()));
diff --git a/compiler/optimizing/scheduler_arm.cc b/compiler/optimizing/scheduler_arm.cc
index ea15790..d6eb6e3 100644
--- a/compiler/optimizing/scheduler_arm.cc
+++ b/compiler/optimizing/scheduler_arm.cc
@@ -20,6 +20,7 @@
 #include "code_generator_utils.h"
 #include "common_arm.h"
 #include "mirror/array-inl.h"
+#include "mirror/string.h"
 
 namespace art {
 namespace arm {
diff --git a/compiler/optimizing/scheduler_arm64.cc b/compiler/optimizing/scheduler_arm64.cc
index f54d3f3..510619f 100644
--- a/compiler/optimizing/scheduler_arm64.cc
+++ b/compiler/optimizing/scheduler_arm64.cc
@@ -18,6 +18,7 @@
 
 #include "code_generator_utils.h"
 #include "mirror/array-inl.h"
+#include "mirror/string.h"
 
 namespace art {
 namespace arm64 {
diff --git a/compiler/verifier_deps_test.cc b/compiler/verifier_deps_test.cc
index 72e2a6c..6538925 100644
--- a/compiler/verifier_deps_test.cc
+++ b/compiler/verifier_deps_test.cc
@@ -87,13 +87,13 @@
     TimingLogger timings("Verify", false, false);
     // The compiler driver handles the verifier deps in the callbacks, so
     // remove what this class did for unit testing.
-    verifier_deps_.reset(nullptr);
+    if (deps == nullptr) {
+      // Create some verifier deps by default if they are not already specified.
+      deps = new verifier::VerifierDeps(dex_files_);
+      verifier_deps_.reset(deps);
+    }
     callbacks_->SetVerifierDeps(deps);
     compiler_driver_->Verify(class_loader_, dex_files_, &timings);
-    // The compiler driver may have updated the VerifierDeps in the callback object.
-    if (callbacks_->GetVerifierDeps() != deps) {
-      verifier_deps_.reset(callbacks_->GetVerifierDeps());
-    }
     callbacks_->SetVerifierDeps(nullptr);
     // Clear entries in the verification results to avoid hitting a DCHECK that
     // we always succeed inserting a new entry after verifying.
@@ -128,6 +128,7 @@
     for (const DexFile* dex_file : dex_files_) {
       compiler_driver_->GetVerificationResults()->AddDexFile(dex_file);
     }
+    compiler_driver_->SetDexFilesForOatFile(dex_files_);
   }
 
   void LoadDexFile(ScopedObjectAccess* soa) REQUIRES_SHARED(Locks::mutator_lock_) {
@@ -228,8 +229,7 @@
         hs.NewHandle(soa.Decode<mirror::ClassLoader>(class_loader_)));
     MutableHandle<mirror::Class> cls(hs.NewHandle<mirror::Class>(nullptr));
     for (const DexFile* dex_file : dex_files_) {
-      const std::vector<dex::TypeIndex>& unverified_classes = deps.GetUnverifiedClasses(*dex_file);
-      std::set<dex::TypeIndex> set(unverified_classes.begin(), unverified_classes.end());
+      const std::set<dex::TypeIndex>& unverified_classes = deps.GetUnverifiedClasses(*dex_file);
       for (uint32_t i = 0; i < dex_file->NumClassDefs(); ++i) {
         const DexFile::ClassDef& class_def = dex_file->GetClassDef(i);
         const char* descriptor = dex_file->GetClassDescriptor(class_def);
@@ -237,7 +237,7 @@
         if (cls == nullptr) {
           CHECK(soa.Self()->IsExceptionPending());
           soa.Self()->ClearException();
-        } else if (set.find(class_def.class_idx_) == set.end()) {
+        } else if (unverified_classes.find(class_def.class_idx_) == unverified_classes.end()) {
           ASSERT_EQ(cls->GetStatus(), mirror::Class::kStatusVerified);
         } else {
           ASSERT_LT(cls->GetStatus(), mirror::Class::kStatusVerified);
@@ -1144,6 +1144,39 @@
   ASSERT_TRUE(HasUnverifiedClass("LMyClassWithNoSuperButFailures;"));
 }
 
+TEST_F(VerifierDepsTest, UnverifiedOrder) {
+  ScopedObjectAccess soa(Thread::Current());
+  jobject loader = LoadDex("VerifierDeps");
+  std::vector<const DexFile*> dex_files = GetDexFiles(loader);
+  ASSERT_GT(dex_files.size(), 0u);
+  const DexFile* dex_file = dex_files[0];
+  VerifierDeps deps1(dex_files);
+  Thread* const self = Thread::Current();
+  ASSERT_TRUE(self->GetVerifierDeps() == nullptr);
+  self->SetVerifierDeps(&deps1);
+  deps1.MaybeRecordVerificationStatus(*dex_file,
+                                      dex::TypeIndex(0),
+                                      verifier::FailureKind::kHardFailure);
+  deps1.MaybeRecordVerificationStatus(*dex_file,
+                                      dex::TypeIndex(1),
+                                      verifier::FailureKind::kHardFailure);
+  VerifierDeps deps2(dex_files);
+  self->SetVerifierDeps(nullptr);
+  self->SetVerifierDeps(&deps2);
+  deps2.MaybeRecordVerificationStatus(*dex_file,
+                                      dex::TypeIndex(1),
+                                      verifier::FailureKind::kHardFailure);
+  deps2.MaybeRecordVerificationStatus(*dex_file,
+                                      dex::TypeIndex(0),
+                                      verifier::FailureKind::kHardFailure);
+  self->SetVerifierDeps(nullptr);
+  std::vector<uint8_t> buffer1;
+  deps1.Encode(dex_files, &buffer1);
+  std::vector<uint8_t> buffer2;
+  deps2.Encode(dex_files, &buffer2);
+  EXPECT_EQ(buffer1, buffer2);
+}
+
 TEST_F(VerifierDepsTest, VerifyDeps) {
   VerifyDexFile();
 
@@ -1441,7 +1474,6 @@
         ASSERT_FALSE(verifier_deps_ == nullptr);
         ASSERT_FALSE(verifier_deps_->Equals(decoded_deps));
       } else {
-        ASSERT_TRUE(verifier_deps_ == nullptr);
         VerifyClassStatus(decoded_deps);
       }
     }
diff --git a/dex2oat/dex2oat.cc b/dex2oat/dex2oat.cc
index 3cc41a6..ff193e9 100644
--- a/dex2oat/dex2oat.cc
+++ b/dex2oat/dex2oat.cc
@@ -65,8 +65,10 @@
 #include "elf_writer_quick.h"
 #include "gc/space/image_space.h"
 #include "gc/space/space-inl.h"
+#include "gc/verification.h"
 #include "image_writer.h"
 #include "interpreter/unstarted_runtime.h"
+#include "java_vm_ext.h"
 #include "jit/profile_compilation_info.h"
 #include "leb128.h"
 #include "linker/buffered_output_stream.h"
@@ -353,6 +355,9 @@
   UsageError("");
   UsageError("  --debuggable: Produce code debuggable with Java debugger.");
   UsageError("");
+  UsageError("  --avoid-storing-invocation: Avoid storing the invocation args in the key value");
+  UsageError("      store. Used to test determinism with different args.");
+  UsageError("");
   UsageError("  --runtime-arg <argument>: used to specify various arguments for the runtime,");
   UsageError("      such as initial heap size, maximum heap size, and verbose output.");
   UsageError("      Use a separate --runtime-arg switch for each argument.");
@@ -406,17 +411,17 @@
   UsageError("");
   UsageError("  --class-loader-context=<string spec>: a string specifying the intended");
   UsageError("      runtime loading context for the compiled dex files.");
-  UsageError("      ");
+  UsageError("");
   UsageError("      It describes how the class loader chain should be built in order to ensure");
   UsageError("      classes are resolved during dex2aot as they would be resolved at runtime.");
   UsageError("      This spec will be encoded in the oat file. If at runtime the dex file is");
   UsageError("      loaded in a different context, the oat file will be rejected.");
-  UsageError("      ");
+  UsageError("");
   UsageError("      The chain is interpreted in the natural 'parent order', meaning that class");
   UsageError("      loader 'i+1' will be the parent of class loader 'i'.");
   UsageError("      The compilation sources will be appended to the classpath of the first class");
   UsageError("      loader.");
-  UsageError("      ");
+  UsageError("");
   UsageError("      E.g. if the context is 'PCL[lib1.dex];DLC[lib2.dex]' and ");
   UsageError("      --dex-file=src.dex then dex2oat will setup a PathClassLoader with classpath ");
   UsageError("      'lib1.dex:src.dex' and set its parent to a DelegateLastClassLoader with ");
@@ -426,9 +431,12 @@
   UsageError("      with --dex-file are found in the classpath. The source dex files will be");
   UsageError("      removed from any class loader's classpath possibly resulting in empty");
   UsageError("      class loaders.");
-  UsageError("      ");
+  UsageError("");
   UsageError("      Example: --class-loader-context=PCL[lib1.dex:lib2.dex];DLC[lib3.dex]");
   UsageError("");
+  UsageError("  --dirty-image-objects=<directory-path>: list of known dirty objects in the image.");
+  UsageError("      The image writer will group them together.");
+  UsageError("");
   std::cerr << "See log for usage error information\n";
   exit(EXIT_FAILURE);
 }
@@ -591,9 +599,9 @@
       compiled_methods_zip_filename_(nullptr),
       compiled_methods_filename_(nullptr),
       passes_to_run_filename_(nullptr),
+      dirty_image_objects_filename_(nullptr),
       multi_image_(false),
       is_host_(false),
-      class_loader_(nullptr),
       elf_writers_(),
       oat_writers_(),
       rodata_(),
@@ -606,6 +614,7 @@
       dump_passes_(false),
       dump_timing_(false),
       dump_slow_timing_(kIsDebugBuild),
+      avoid_storing_invocation_(false),
       swap_fd_(kInvalidFd),
       app_image_fd_(kInvalidFd),
       profile_file_fd_(kInvalidFd),
@@ -1128,14 +1137,16 @@
 
   void InsertCompileOptions(int argc, char** argv) {
     std::ostringstream oss;
-    for (int i = 0; i < argc; ++i) {
-      if (i > 0) {
-        oss << ' ';
+    if (!avoid_storing_invocation_) {
+      for (int i = 0; i < argc; ++i) {
+        if (i > 0) {
+          oss << ' ';
+        }
+        oss << argv[i];
       }
-      oss << argv[i];
+      key_value_store_->Put(OatHeader::kDex2OatCmdLineKey, oss.str());
+      oss.str("");  // Reset.
     }
-    key_value_store_->Put(OatHeader::kDex2OatCmdLineKey, oss.str());
-    oss.str("");  // Reset.
     oss << kRuntimeISA;
     key_value_store_->Put(OatHeader::kDex2OatHostKey, oss.str());
     key_value_store_->Put(
@@ -1266,6 +1277,8 @@
         dump_passes_ = true;
       } else if (option == "--dump-stats") {
         dump_stats_ = true;
+      } else if (option == "--avoid-storing-invocation") {
+        avoid_storing_invocation_ = true;
       } else if (option.starts_with("--swap-file=")) {
         swap_file_name_ = option.substr(strlen("--swap-file=")).data();
       } else if (option.starts_with("--swap-fd=")) {
@@ -1303,9 +1316,11 @@
       } else if (option.starts_with("--class-loader-context=")) {
         class_loader_context_ = ClassLoaderContext::Create(
             option.substr(strlen("--class-loader-context=")).data());
-        if (class_loader_context_== nullptr) {
+        if (class_loader_context_ == nullptr) {
           Usage("Option --class-loader-context has an incorrect format: %s", option.data());
         }
+      } else if (option.starts_with("--dirty-image-objects=")) {
+        dirty_image_objects_filename_ = option.substr(strlen("--dirty-image-objects=")).data();
       } else if (!compiler_options_->ParseCompilerOption(option, Usage)) {
         Usage("Unknown argument %s", option.data());
       }
@@ -1484,14 +1499,6 @@
     }
   }
 
-  void Shutdown() {
-    ScopedObjectAccess soa(Thread::Current());
-    for (jobject dex_cache : dex_caches_) {
-      soa.Env()->DeleteLocalRef(dex_cache);
-    }
-    dex_caches_.clear();
-  }
-
   void LoadClassProfileDescriptors() {
     if (profile_compilation_info_ != nullptr && IsImage()) {
       Runtime* runtime = Runtime::Current();
@@ -1515,7 +1522,8 @@
   dex2oat::ReturnCode Setup() {
     TimingLogger::ScopedTiming t("dex2oat Setup", timings_);
 
-    if (!PrepareImageClasses() || !PrepareCompiledClasses() || !PrepareCompiledMethods()) {
+    if (!PrepareImageClasses() || !PrepareCompiledClasses() || !PrepareCompiledMethods() ||
+        !PrepareDirtyObjects()) {
       return dex2oat::ReturnCode::kOther;
     }
 
@@ -1576,20 +1584,12 @@
       }
 
       // Open dex files for class path.
+
       if (class_loader_context_ == nullptr) {
-        // TODO(calin): Temporary workaround while we transition to use
-        // --class-loader-context instead of --runtime-arg -cp
-        if (runtime_->GetClassPathString().empty()) {
-          class_loader_context_ = std::unique_ptr<ClassLoaderContext>(
-              new ClassLoaderContext());
-        } else {
-          std::string spec = runtime_->GetClassPathString() == OatFile::kSpecialSharedLibrary
-              ? OatFile::kSpecialSharedLibrary
-              : "PCL[" + runtime_->GetClassPathString() + "]";
-          class_loader_context_ = ClassLoaderContext::Create(spec);
-        }
+        // If no context was specified use the default one (which is an empty PathClassLoader).
+        class_loader_context_ = std::unique_ptr<ClassLoaderContext>(ClassLoaderContext::Default());
       }
-      CHECK(class_loader_context_ != nullptr);
+
       DCHECK_EQ(oat_writers_.size(), 1u);
 
       // Note: Ideally we would reject context where the source dex files are also
@@ -1660,6 +1660,8 @@
 
     // If we need to downgrade the compiler-filter for size reasons.
     if (!IsBootImage() && IsVeryLarge(dex_files_)) {
+      // If we need to downgrade the compiler-filter for size reasons, do that early before we read
+      // it below for creating verification callbacks.
       if (!CompilerFilter::IsAsGoodAs(kLargeAppFilter, compiler_options_->GetCompilerFilter())) {
         LOG(INFO) << "Very large app, downgrading to verify.";
         // Note: this change won't be reflected in the key-value store, as that had to be
@@ -1712,13 +1714,11 @@
     Thread* self = Thread::Current();
     WellKnownClasses::Init(self->GetJniEnv());
 
-    ClassLinker* const class_linker = Runtime::Current()->GetClassLinker();
     if (!IsBootImage()) {
       constexpr bool kSaveDexInput = false;
       if (kSaveDexInput) {
         SaveDexInput();
       }
-      class_loader_ = class_loader_context_->CreateClassLoader(dex_files_);
     }
 
     // Ensure opened dex files are writable for dex-to-dex transformations.
@@ -1729,24 +1729,12 @@
       }
     }
 
-    // Ensure that the dex caches stay live since we don't want class unloading
-    // to occur during compilation.
-    for (const auto& dex_file : dex_files_) {
-      ScopedObjectAccess soa(self);
-      dex_caches_.push_back(soa.AddLocalReference<jobject>(
-          class_linker->RegisterDexFile(*dex_file,
-                                        soa.Decode<mirror::ClassLoader>(class_loader_).Ptr())));
-      if (dex_caches_.back() == nullptr) {
-        soa.Self()->AssertPendingException();
-        soa.Self()->ClearException();
-        PLOG(ERROR) << "Failed to register dex file.";
-        return dex2oat::ReturnCode::kOther;
-      }
-      // Pre-register dex files so that we can access verification results without locks during
-      // compilation and verification.
-      if (verification_results_ != nullptr) {
-        // Verification results are only required for modes that have any compilation. Avoid
-        // adding the dex files if possible to prevent allocating large arrays.
+    // Verification results are only required for modes that have any compilation. Avoid
+    // adding the dex files if possible to prevent allocating large arrays.
+    if (verification_results_ != nullptr) {
+      for (const auto& dex_file : dex_files_) {
+        // Pre-register dex files so that we can access verification results without locks during
+        // compilation and verification.
         verification_results_->AddDexFile(dex_file);
       }
     }
@@ -1759,13 +1747,50 @@
     return IsImage() && oat_fd_ != kInvalidFd;
   }
 
-  // Create and invoke the compiler driver. This will compile all the dex files.
-  void Compile() {
+  // Doesn't return the class loader since it's not meant to be used for image compilation.
+  void CompileDexFilesIndividually() {
+    CHECK(!IsImage()) << "Not supported with image";
+    for (const DexFile* dex_file : dex_files_) {
+      std::vector<const DexFile*> dex_files(1u, dex_file);
+      VLOG(compiler) << "Compiling " << dex_file->GetLocation();
+      jobject class_loader = CompileDexFiles(dex_files);
+      CHECK(class_loader != nullptr);
+      ScopedObjectAccess soa(Thread::Current());
+      // Unload class loader to free RAM.
+      jweak weak_class_loader = soa.Env()->vm->AddWeakGlobalRef(
+          soa.Self(),
+          soa.Decode<mirror::ClassLoader>(class_loader));
+      soa.Env()->vm->DeleteGlobalRef(soa.Self(), class_loader);
+      runtime_->GetHeap()->CollectGarbage(/*clear_soft_references*/ true);
+      ObjPtr<mirror::ClassLoader> decoded_weak = soa.Decode<mirror::ClassLoader>(weak_class_loader);
+      if (decoded_weak != nullptr) {
+        LOG(FATAL) << "Failed to unload class loader, path from root set: "
+                   << runtime_->GetHeap()->GetVerification()->FirstPathFromRootSet(decoded_weak);
+      }
+      VLOG(compiler) << "Unloaded classloader";
+    }
+  }
+
+  bool ShouldCompileDexFilesIndividually() const {
+    // Compile individually if we are not building an image, not using any compilation, and are
+    // using multidex.
+    // This means extract, verify, and quicken will use the individual compilation mode (to reduce
+    // RAM used by the compiler).
+    // TODO: Still do it for app images to get testing coverage. Note that this will generate empty
+    // app images.
+    return !IsImage() &&
+        dex_files_.size() > 1 &&
+        !CompilerFilter::IsAnyCompilationEnabled(compiler_options_->GetCompilerFilter());
+  }
+
+  // Set up and create the compiler driver and then invoke it to compile all the dex files.
+  jobject Compile() {
+    ClassLinker* const class_linker = Runtime::Current()->GetClassLinker();
+
     TimingLogger::ScopedTiming t("dex2oat Compile", timings_);
     compiler_phases_timings_.reset(new CumulativeLogger("compilation times"));
 
     // Find the dex files we should not inline from.
-
     std::vector<std::string> no_inline_filters;
     Split(no_inline_from_string_, ',', &no_inline_filters);
 
@@ -1776,7 +1801,6 @@
     }
 
     if (!no_inline_filters.empty()) {
-      ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
       std::vector<const DexFile*> class_path_files;
       if (!IsBootImage()) {
         // The class loader context is used only for apps.
@@ -1842,8 +1866,46 @@
       // experimentation.
       TimingLogger::ScopedTiming time_unquicken("Unquicken", timings_);
       VdexFile::Unquicken(dex_files_, input_vdex_file_->GetQuickeningInfo());
+    } else {
+      // Create the main VerifierDeps, here instead of in the compiler since we want to aggregate
+      // the results for all the dex files, not just the results for the current dex file.
+      callbacks_->SetVerifierDeps(new verifier::VerifierDeps(dex_files_));
     }
-    driver_->CompileAll(class_loader_, dex_files_, timings_);
+    // Invoke the compilation.
+    if (ShouldCompileDexFilesIndividually()) {
+      CompileDexFilesIndividually();
+      // Return a null classloader since we already freed released it.
+      return nullptr;
+    }
+    return CompileDexFiles(dex_files_);
+  }
+
+  // Create the class loader, use it to compile, and return.
+  jobject CompileDexFiles(const std::vector<const DexFile*>& dex_files) {
+    ClassLinker* const class_linker = Runtime::Current()->GetClassLinker();
+
+    jobject class_loader = nullptr;
+    if (!IsBootImage()) {
+      class_loader = class_loader_context_->CreateClassLoader(dex_files_);
+    }
+
+    // Register dex caches and key them to the class loader so that they only unload when the
+    // class loader unloads.
+    for (const auto& dex_file : dex_files) {
+      ScopedObjectAccess soa(Thread::Current());
+      // Registering the dex cache adds a strong root in the class loader that prevents the dex
+      // cache from being unloaded early.
+      ObjPtr<mirror::DexCache> dex_cache = class_linker->RegisterDexFile(
+          *dex_file,
+          soa.Decode<mirror::ClassLoader>(class_loader));
+      if (dex_cache == nullptr) {
+        soa.Self()->AssertPendingException();
+        LOG(FATAL) << "Failed to register dex file " << dex_file->GetLocation() << " "
+                   << soa.Self()->GetException()->Dump();
+      }
+    }
+    driver_->CompileAll(class_loader, dex_files, timings_);
+    return class_loader;
   }
 
   // Notes on the interleaving of creating the images and oat files to
@@ -1947,7 +2009,8 @@
                                           IsAppImage(),
                                           image_storage_mode_,
                                           oat_filenames_,
-                                          dex_file_oat_index_map_));
+                                          dex_file_oat_index_map_,
+                                          dirty_image_objects_.get()));
 
       // We need to prepare method offsets in the image address space for direct method patching.
       TimingLogger::ScopedTiming t2("dex2oat Prepare image address space", timings_);
@@ -2373,6 +2436,22 @@
     return true;
   }
 
+  bool PrepareDirtyObjects() {
+    if (dirty_image_objects_filename_ != nullptr) {
+      dirty_image_objects_.reset(ReadCommentedInputFromFile<std::unordered_set<std::string>>(
+          dirty_image_objects_filename_,
+          nullptr));
+      if (dirty_image_objects_ == nullptr) {
+        LOG(ERROR) << "Failed to create list of dirty objects from '"
+            << dirty_image_objects_filename_ << "'";
+        return false;
+      }
+    } else {
+      dirty_image_objects_.reset(nullptr);
+    }
+    return true;
+  }
+
   void PruneNonExistentDexFiles() {
     DCHECK_EQ(dex_filenames_.size(), dex_locations_.size());
     size_t kept = 0u;
@@ -2790,9 +2869,11 @@
   const char* compiled_methods_zip_filename_;
   const char* compiled_methods_filename_;
   const char* passes_to_run_filename_;
+  const char* dirty_image_objects_filename_;
   std::unique_ptr<std::unordered_set<std::string>> image_classes_;
   std::unique_ptr<std::unordered_set<std::string>> compiled_classes_;
   std::unique_ptr<std::unordered_set<std::string>> compiled_methods_;
+  std::unique_ptr<std::unordered_set<std::string>> dirty_image_objects_;
   std::unique_ptr<std::vector<std::string>> passes_to_run_;
   bool multi_image_;
   bool is_host_;
@@ -2800,8 +2881,6 @@
   // Dex files we are compiling, does not include the class path dex files.
   std::vector<const DexFile*> dex_files_;
   std::string no_inline_from_string_;
-  std::vector<jobject> dex_caches_;
-  jobject class_loader_;
 
   std::vector<std::unique_ptr<ElfWriter>> elf_writers_;
   std::vector<std::unique_ptr<OatWriter>> oat_writers_;
@@ -2820,6 +2899,7 @@
   bool dump_passes_;
   bool dump_timing_;
   bool dump_slow_timing_;
+  bool avoid_storing_invocation_;
   std::string swap_file_name_;
   int swap_fd_;
   size_t min_dex_files_for_swap_ = kDefaultMinDexFilesForSwap;
@@ -2870,9 +2950,23 @@
 #endif
 }
 
+class ScopedGlobalRef {
+ public:
+  explicit ScopedGlobalRef(jobject obj) : obj_(obj) {}
+  ~ScopedGlobalRef() {
+    if (obj_ != nullptr) {
+      ScopedObjectAccess soa(Thread::Current());
+      soa.Env()->vm->DeleteGlobalRef(soa.Self(), obj_);
+    }
+  }
+
+ private:
+  jobject obj_;
+};
+
 static dex2oat::ReturnCode CompileImage(Dex2Oat& dex2oat) {
   dex2oat.LoadClassProfileDescriptors();
-  dex2oat.Compile();
+  ScopedGlobalRef class_loader(dex2oat.Compile());
 
   if (!dex2oat.WriteOutputFiles()) {
     dex2oat.EraseOutputFiles();
@@ -2920,7 +3014,7 @@
 }
 
 static dex2oat::ReturnCode CompileApp(Dex2Oat& dex2oat) {
-  dex2oat.Compile();
+  ScopedGlobalRef class_loader(dex2oat.Compile());
 
   if (!dex2oat.WriteOutputFiles()) {
     dex2oat.EraseOutputFiles();
@@ -3014,7 +3108,6 @@
     result = CompileApp(*dex2oat);
   }
 
-  dex2oat->Shutdown();
   return result;
 }
 }  // namespace art
diff --git a/dex2oat/dex2oat_image_test.cc b/dex2oat/dex2oat_image_test.cc
index 95fb16d..46c5f58 100644
--- a/dex2oat/dex2oat_image_test.cc
+++ b/dex2oat/dex2oat_image_test.cc
@@ -340,6 +340,15 @@
     // EXPECT_GE(profile_sizes.oat_size / kRatio, compiled_methods_sizes.oat_size);
     EXPECT_GE(profile_sizes.vdex_size / kRatio, compiled_methods_sizes.vdex_size);
   }
+  // Test dirty image objects.
+  {
+    ScratchFile classes;
+    GenerateClasses(classes.GetFile(), /*frequency*/ 1u);
+    image_classes_sizes = CompileImageAndGetSizes(
+        {"--dirty-image-objects=" + classes.GetFilename()});
+    classes.Close();
+    std::cout << "Dirty image object sizes " << image_classes_sizes << std::endl;
+  }
 }
 
 }  // namespace art
diff --git a/dex2oat/dex2oat_test.cc b/dex2oat/dex2oat_test.cc
index 32877a8..12bceb3 100644
--- a/dex2oat/dex2oat_test.cc
+++ b/dex2oat/dex2oat_test.cc
@@ -134,7 +134,7 @@
     }
   }
 
-  // Check the input compiler filter against the generated oat file's filter. Mayb be overridden
+  // Check the input compiler filter against the generated oat file's filter. May be overridden
   // in subclasses when equality is not expected.
   virtual void CheckFilter(CompilerFilter::Filter expected, CompilerFilter::Filter actual) {
     EXPECT_EQ(expected, actual);
@@ -153,14 +153,7 @@
 
     std::vector<std::string> argv;
     argv.push_back(runtime->GetCompilerExecutable());
-    argv.push_back("--runtime-arg");
-    argv.push_back("-classpath");
-    argv.push_back("--runtime-arg");
-    std::string class_path = runtime->GetClassPathString();
-    if (class_path == "") {
-      class_path = OatFile::kSpecialSharedLibrary;
-    }
-    argv.push_back(class_path);
+
     if (runtime->IsJavaDebuggable()) {
       argv.push_back("--debuggable");
     }
diff --git a/imgdiag/imgdiag.cc b/imgdiag/imgdiag.cc
index fb8e894..9ffc414 100644
--- a/imgdiag/imgdiag.cc
+++ b/imgdiag/imgdiag.cc
@@ -333,9 +333,11 @@
                         std::vector<uint8_t>* remote_contents,
                         std::vector<uint8_t>* zygote_contents,
                         const backtrace_map_t& boot_map,
-                        const ImageHeader& image_header) :
-    RegionCommon<mirror::Object>(os, remote_contents, zygote_contents, boot_map, image_header),
-    os_(*os) { }
+                        const ImageHeader& image_header,
+                        bool dump_dirty_objects)
+      : RegionCommon<mirror::Object>(os, remote_contents, zygote_contents, boot_map, image_header),
+        os_(*os),
+        dump_dirty_objects_(dump_dirty_objects) { }
 
   void CheckEntrySanity(const uint8_t* current) const
       REQUIRES_SHARED(Locks::mutator_lock_) {
@@ -396,7 +398,10 @@
     class_data_[klass].AddDirtyObject(entry, entry_remote);
   }
 
-  void DiffEntryContents(mirror::Object* entry, uint8_t* remote_bytes, const uint8_t* base_ptr)
+  void DiffEntryContents(mirror::Object* entry,
+                         uint8_t* remote_bytes,
+                         const uint8_t* base_ptr,
+                         bool log_dirty_objects)
       REQUIRES_SHARED(Locks::mutator_lock_) {
     const char* tabs = "    ";
     // Attempt to find fields for all dirty bytes.
@@ -453,6 +458,9 @@
       }
     }
     if (!dirty_static_fields.empty()) {
+      if (dump_dirty_objects_ && log_dirty_objects) {
+        dirty_objects_.insert(entry);
+      }
       os_ << tabs << "Dirty static fields " << dirty_static_fields.size() << "\n";
       for (ArtField* field : dirty_static_fields) {
         os_ << tabs << ArtField::PrettyField(field)
@@ -463,6 +471,14 @@
     os_ << "\n";
   }
 
+  void DumpDirtyObjects() REQUIRES_SHARED(Locks::mutator_lock_) {
+    for (mirror::Object* obj : dirty_objects_) {
+      if (obj->IsClass()) {
+        os_ << "Private dirty object: " << obj->AsClass()->PrettyDescriptor() << "\n";
+      }
+    }
+  }
+
   void DumpDirtyEntries() REQUIRES_SHARED(Locks::mutator_lock_) {
     // vector of pairs (size_t count, Class*)
     auto dirty_object_class_values =
@@ -592,6 +608,8 @@
   };
 
   std::ostream& os_;
+  bool dump_dirty_objects_;
+  std::unordered_set<mirror::Object*> dirty_objects_;
   std::map<mirror::Class*, ClassData> class_data_;
 
   DISALLOW_COPY_AND_ASSIGN(RegionSpecializedBase);
@@ -720,9 +738,15 @@
              std::vector<uint8_t>* remote_contents,
              std::vector<uint8_t>* zygote_contents,
              const backtrace_map_t& boot_map,
-             const ImageHeader& image_header) :
-    RegionSpecializedBase<T>(os, remote_contents, zygote_contents, boot_map, image_header),
-    os_(*os) {
+             const ImageHeader& image_header,
+             bool dump_dirty_objects)
+      : RegionSpecializedBase<T>(os,
+                                 remote_contents,
+                                 zygote_contents,
+                                 boot_map,
+                                 image_header,
+                                 dump_dirty_objects),
+        os_(*os) {
     CHECK(remote_contents != nullptr);
     CHECK(zygote_contents != nullptr);
   }
@@ -773,7 +797,8 @@
     DiffDirtyEntries(ProcessType::kRemote,
                      begin_image_ptr,
                      RegionCommon<T>::remote_contents_,
-                     base_ptr);
+                     base_ptr,
+                     /*log_dirty_objects*/true);
     // Print shared dirty after since it's less important.
     if (RegionCommon<T>::GetZygoteDirtyEntryCount() != 0) {
       // We only reach this point if both pids were specified.  Furthermore,
@@ -784,8 +809,10 @@
       DiffDirtyEntries(ProcessType::kZygote,
                        begin_image_ptr,
                        RegionCommon<T>::zygote_contents_,
-                       begin_image_ptr);
+                       begin_image_ptr,
+                       /*log_dirty_objects*/false);
     }
+    RegionSpecializedBase<T>::DumpDirtyObjects();
     RegionSpecializedBase<T>::DumpDirtyEntries();
     RegionSpecializedBase<T>::DumpFalseDirtyEntries();
     RegionSpecializedBase<T>::DumpCleanEntries();
@@ -797,7 +824,8 @@
   void DiffDirtyEntries(ProcessType process_type,
                         const uint8_t* begin_image_ptr,
                         std::vector<uint8_t>* contents,
-                        const uint8_t* base_ptr)
+                        const uint8_t* base_ptr,
+                        bool log_dirty_objects)
       REQUIRES_SHARED(Locks::mutator_lock_) {
     os_ << RegionCommon<T>::dirty_entries_.size() << "\n";
     const std::set<T*>& entries =
@@ -808,7 +836,10 @@
       uint8_t* entry_bytes = reinterpret_cast<uint8_t*>(entry);
       ptrdiff_t offset = entry_bytes - begin_image_ptr;
       uint8_t* remote_bytes = &(*contents)[offset];
-      RegionSpecializedBase<T>::DiffEntryContents(entry, remote_bytes, &base_ptr[offset]);
+      RegionSpecializedBase<T>::DiffEntryContents(entry,
+                                                  remote_bytes,
+                                                  &base_ptr[offset],
+                                                  log_dirty_objects);
     }
   }
 
@@ -872,12 +903,14 @@
                          const ImageHeader& image_header,
                          const std::string& image_location,
                          pid_t image_diff_pid,
-                         pid_t zygote_diff_pid)
+                         pid_t zygote_diff_pid,
+                         bool dump_dirty_objects)
       : os_(os),
         image_header_(image_header),
         image_location_(image_location),
         image_diff_pid_(image_diff_pid),
         zygote_diff_pid_(zygote_diff_pid),
+        dump_dirty_objects_(dump_dirty_objects),
         zygote_pid_only_(false) {}
 
   bool Init() {
@@ -1075,6 +1108,8 @@
       }
     }
 
+    std::vector<size_t> private_dirty_pages_for_section(ImageHeader::kSectionCount, 0u);
+
     // Iterate through one byte at a time.
     ptrdiff_t page_off_begin = image_header_.GetImageBegin() - image_begin;
     for (uintptr_t begin = boot_map_.start; begin != boot_map_.end; ++begin) {
@@ -1127,6 +1162,12 @@
 
         if (is_dirty && is_private) {
           mapping_data->private_dirty_pages++;
+          for (size_t i = 0; i < ImageHeader::kSectionCount; ++i) {
+            const ImageHeader::ImageSections section = static_cast<ImageHeader::ImageSections>(i);
+            if (image_header_.GetImageSection(section).Contains(offset)) {
+              ++private_dirty_pages_for_section[i];
+            }
+          }
         }
       }
     }
@@ -1138,7 +1179,19 @@
        << mapping_data->dirty_pages << " pages are dirty;\n  "
        << mapping_data->false_dirty_pages << " pages are false dirty;\n  "
        << mapping_data->private_pages << " pages are private;\n  "
-       << mapping_data->private_dirty_pages << " pages are Private_Dirty\n  ";
+       << mapping_data->private_dirty_pages << " pages are Private_Dirty\n  "
+       << "\n";
+
+    size_t total_private_dirty_pages = std::accumulate(private_dirty_pages_for_section.begin(),
+                                                       private_dirty_pages_for_section.end(),
+                                                       0u);
+    os << "Image sections (total private dirty pages " << total_private_dirty_pages << ")\n";
+    for (size_t i = 0; i < ImageHeader::kSectionCount; ++i) {
+      const ImageHeader::ImageSections section = static_cast<ImageHeader::ImageSections>(i);
+      os << section << " " << image_header_.GetImageSection(section)
+         << " private dirty pages=" << private_dirty_pages_for_section[i] << "\n";
+    }
+    os << "\n";
 
     return true;
   }
@@ -1187,7 +1240,8 @@
                                                   &remote_contents_,
                                                   &zygote_contents_,
                                                   boot_map_,
-                                                  image_header_);
+                                                  image_header_,
+                                                  dump_dirty_objects_);
 
     RemoteProcesses remotes;
     if (zygote_pid_only_) {
@@ -1344,6 +1398,7 @@
   const std::string image_location_;
   pid_t image_diff_pid_;  // Dump image diff against boot.art if pid is non-negative
   pid_t zygote_diff_pid_;  // Dump image diff against zygote boot.art if pid is non-negative
+  bool dump_dirty_objects_;  // Adds dumping of objects that are dirty.
   bool zygote_pid_only_;  // The user only specified a pid for the zygote.
 
   // BacktraceMap used for finding the memory mapping of the image file.
@@ -1371,7 +1426,8 @@
 static int DumpImage(Runtime* runtime,
                      std::ostream* os,
                      pid_t image_diff_pid,
-                     pid_t zygote_diff_pid) {
+                     pid_t zygote_diff_pid,
+                     bool dump_dirty_objects) {
   ScopedObjectAccess soa(Thread::Current());
   gc::Heap* heap = runtime->GetHeap();
   std::vector<gc::space::ImageSpace*> image_spaces = heap->GetBootImageSpaces();
@@ -1387,7 +1443,8 @@
                                   image_header,
                                   image_space->GetImageLocation(),
                                   image_diff_pid,
-                                  zygote_diff_pid);
+                                  zygote_diff_pid,
+                                  dump_dirty_objects);
     if (!img_diag_dumper.Init()) {
       return EXIT_FAILURE;
     }
@@ -1425,6 +1482,8 @@
         *error_msg = "Zygote diff pid out of range";
         return kParseError;
       }
+    } else if (option == "--dump-dirty-objects") {
+      dump_dirty_objects_ = true;
     } else {
       return kParseUnknownArgument;
     }
@@ -1477,6 +1536,7 @@
         "  --zygote-diff-pid=<pid>: provide the PID of the zygote whose boot.art you want to diff "
         "against.\n"
         "      Example: --zygote-diff-pid=$(pid zygote)\n"
+        "  --dump-dirty-objects: additionally output dirty objects of interest.\n"
         "\n";
 
     return usage;
@@ -1485,6 +1545,7 @@
  public:
   pid_t image_diff_pid_ = -1;
   pid_t zygote_diff_pid_ = -1;
+  bool dump_dirty_objects_ = false;
 };
 
 struct ImgDiagMain : public CmdlineMain<ImgDiagArgs> {
@@ -1494,7 +1555,8 @@
     return DumpImage(runtime,
                      args_->os_,
                      args_->image_diff_pid_,
-                     args_->zygote_diff_pid_) == EXIT_SUCCESS;
+                     args_->zygote_diff_pid_,
+                     args_->dump_dirty_objects_) == EXIT_SUCCESS;
   }
 };
 
diff --git a/runtime/openjdkjvm/Android.bp b/openjdkjvm/Android.bp
similarity index 96%
rename from runtime/openjdkjvm/Android.bp
rename to openjdkjvm/Android.bp
index 37112b6..071b434 100644
--- a/runtime/openjdkjvm/Android.bp
+++ b/openjdkjvm/Android.bp
@@ -18,7 +18,6 @@
     defaults: ["art_defaults"],
     host_supported: true,
     srcs: ["OpenjdkJvm.cc"],
-    include_dirs: ["art/runtime"],
     shared_libs: [
         "libbase",
         "libnativehelper"
diff --git a/runtime/openjdkjvm/MODULE_LICENSE_GPL_WITH_CLASSPATH_EXCEPTION b/openjdkjvm/MODULE_LICENSE_GPL_WITH_CLASSPATH_EXCEPTION
similarity index 100%
rename from runtime/openjdkjvm/MODULE_LICENSE_GPL_WITH_CLASSPATH_EXCEPTION
rename to openjdkjvm/MODULE_LICENSE_GPL_WITH_CLASSPATH_EXCEPTION
diff --git a/runtime/openjdkjvm/NOTICE b/openjdkjvm/NOTICE
similarity index 100%
rename from runtime/openjdkjvm/NOTICE
rename to openjdkjvm/NOTICE
diff --git a/runtime/openjdkjvm/OpenjdkJvm.cc b/openjdkjvm/OpenjdkJvm.cc
similarity index 100%
rename from runtime/openjdkjvm/OpenjdkJvm.cc
rename to openjdkjvm/OpenjdkJvm.cc
diff --git a/runtime/openjdkjvmti/Android.bp b/openjdkjvmti/Android.bp
similarity index 97%
rename from runtime/openjdkjvmti/Android.bp
rename to openjdkjvmti/Android.bp
index aec1bd0..b6b1b56 100644
--- a/runtime/openjdkjvmti/Android.bp
+++ b/openjdkjvmti/Android.bp
@@ -48,7 +48,6 @@
            "ti_threadgroup.cc",
            "ti_timers.cc",
            "transform.cc"],
-    include_dirs: ["art/runtime"],
     header_libs: ["libopenjdkjvmti_headers"],
     shared_libs: [
         "libbase",
diff --git a/runtime/openjdkjvmti/MODULE_LICENSE_GPL_WITH_CLASSPATH_EXCEPTION b/openjdkjvmti/MODULE_LICENSE_GPL_WITH_CLASSPATH_EXCEPTION
similarity index 100%
rename from runtime/openjdkjvmti/MODULE_LICENSE_GPL_WITH_CLASSPATH_EXCEPTION
rename to openjdkjvmti/MODULE_LICENSE_GPL_WITH_CLASSPATH_EXCEPTION
diff --git a/runtime/openjdkjvmti/NOTICE b/openjdkjvmti/NOTICE
similarity index 100%
rename from runtime/openjdkjvmti/NOTICE
rename to openjdkjvmti/NOTICE
diff --git a/runtime/openjdkjvmti/OpenjdkJvmTi.cc b/openjdkjvmti/OpenjdkJvmTi.cc
similarity index 95%
rename from runtime/openjdkjvmti/OpenjdkJvmTi.cc
rename to openjdkjvmti/OpenjdkJvmTi.cc
index 3c1311b..af77072 100644
--- a/runtime/openjdkjvmti/OpenjdkJvmTi.cc
+++ b/openjdkjvmti/OpenjdkJvmTi.cc
@@ -503,112 +503,112 @@
   }
 
   static jvmtiError GetLocalObject(jvmtiEnv* env,
-                                   jthread thread ATTRIBUTE_UNUSED,
-                                   jint depth ATTRIBUTE_UNUSED,
-                                   jint slot ATTRIBUTE_UNUSED,
-                                   jobject* value_ptr ATTRIBUTE_UNUSED) {
+                                   jthread thread,
+                                   jint depth,
+                                   jint slot,
+                                   jobject* value_ptr) {
     ENSURE_VALID_ENV(env);
     ENSURE_HAS_CAP(env, can_access_local_variables);
-    return ERR(NOT_IMPLEMENTED);
+    return MethodUtil::GetLocalVariable(env, thread, depth, slot, value_ptr);
   }
 
   static jvmtiError GetLocalInstance(jvmtiEnv* env,
-                                     jthread thread ATTRIBUTE_UNUSED,
-                                     jint depth ATTRIBUTE_UNUSED,
-                                     jobject* value_ptr ATTRIBUTE_UNUSED) {
+                                     jthread thread,
+                                     jint depth,
+                                     jobject* value_ptr) {
     ENSURE_VALID_ENV(env);
     ENSURE_HAS_CAP(env, can_access_local_variables);
-    return ERR(NOT_IMPLEMENTED);
+    return MethodUtil::GetLocalInstance(env, thread, depth, value_ptr);
   }
 
   static jvmtiError GetLocalInt(jvmtiEnv* env,
-                                jthread thread ATTRIBUTE_UNUSED,
-                                jint depth ATTRIBUTE_UNUSED,
-                                jint slot ATTRIBUTE_UNUSED,
-                                jint* value_ptr ATTRIBUTE_UNUSED) {
+                                jthread thread,
+                                jint depth,
+                                jint slot,
+                                jint* value_ptr) {
     ENSURE_VALID_ENV(env);
     ENSURE_HAS_CAP(env, can_access_local_variables);
-    return ERR(NOT_IMPLEMENTED);
+    return MethodUtil::GetLocalVariable(env, thread, depth, slot, value_ptr);
   }
 
   static jvmtiError GetLocalLong(jvmtiEnv* env,
-                                 jthread thread ATTRIBUTE_UNUSED,
-                                 jint depth ATTRIBUTE_UNUSED,
-                                 jint slot ATTRIBUTE_UNUSED,
-                                 jlong* value_ptr ATTRIBUTE_UNUSED) {
+                                 jthread thread,
+                                 jint depth,
+                                 jint slot,
+                                 jlong* value_ptr) {
     ENSURE_VALID_ENV(env);
     ENSURE_HAS_CAP(env, can_access_local_variables);
-    return ERR(NOT_IMPLEMENTED);
+    return MethodUtil::GetLocalVariable(env, thread, depth, slot, value_ptr);
   }
 
   static jvmtiError GetLocalFloat(jvmtiEnv* env,
-                                  jthread thread ATTRIBUTE_UNUSED,
-                                  jint depth ATTRIBUTE_UNUSED,
-                                  jint slot ATTRIBUTE_UNUSED,
-                                  jfloat* value_ptr ATTRIBUTE_UNUSED) {
+                                  jthread thread,
+                                  jint depth,
+                                  jint slot,
+                                  jfloat* value_ptr) {
     ENSURE_VALID_ENV(env);
     ENSURE_HAS_CAP(env, can_access_local_variables);
-    return ERR(NOT_IMPLEMENTED);
+    return MethodUtil::GetLocalVariable(env, thread, depth, slot, value_ptr);
   }
 
   static jvmtiError GetLocalDouble(jvmtiEnv* env,
-                                   jthread thread ATTRIBUTE_UNUSED,
-                                   jint depth ATTRIBUTE_UNUSED,
-                                   jint slot ATTRIBUTE_UNUSED,
-                                   jdouble* value_ptr ATTRIBUTE_UNUSED) {
+                                   jthread thread,
+                                   jint depth,
+                                   jint slot,
+                                   jdouble* value_ptr) {
     ENSURE_VALID_ENV(env);
     ENSURE_HAS_CAP(env, can_access_local_variables);
-    return ERR(NOT_IMPLEMENTED);
+    return MethodUtil::GetLocalVariable(env, thread, depth, slot, value_ptr);
   }
 
   static jvmtiError SetLocalObject(jvmtiEnv* env,
-                                   jthread thread ATTRIBUTE_UNUSED,
-                                   jint depth ATTRIBUTE_UNUSED,
-                                   jint slot ATTRIBUTE_UNUSED,
-                                   jobject value ATTRIBUTE_UNUSED) {
+                                   jthread thread,
+                                   jint depth,
+                                   jint slot,
+                                   jobject value) {
     ENSURE_VALID_ENV(env);
     ENSURE_HAS_CAP(env, can_access_local_variables);
-    return ERR(NOT_IMPLEMENTED);
+    return MethodUtil::SetLocalVariable(env, thread, depth, slot, value);
   }
 
   static jvmtiError SetLocalInt(jvmtiEnv* env,
-                                jthread thread ATTRIBUTE_UNUSED,
-                                jint depth ATTRIBUTE_UNUSED,
-                                jint slot ATTRIBUTE_UNUSED,
-                                jint value ATTRIBUTE_UNUSED) {
+                                jthread thread,
+                                jint depth,
+                                jint slot,
+                                jint value) {
     ENSURE_VALID_ENV(env);
     ENSURE_HAS_CAP(env, can_access_local_variables);
-    return ERR(NOT_IMPLEMENTED);
+    return MethodUtil::SetLocalVariable(env, thread, depth, slot, value);
   }
 
   static jvmtiError SetLocalLong(jvmtiEnv* env,
-                                 jthread thread ATTRIBUTE_UNUSED,
-                                 jint depth ATTRIBUTE_UNUSED,
-                                 jint slot ATTRIBUTE_UNUSED,
-                                 jlong value ATTRIBUTE_UNUSED) {
+                                 jthread thread,
+                                 jint depth,
+                                 jint slot,
+                                 jlong value) {
     ENSURE_VALID_ENV(env);
     ENSURE_HAS_CAP(env, can_access_local_variables);
-    return ERR(NOT_IMPLEMENTED);
+    return MethodUtil::SetLocalVariable(env, thread, depth, slot, value);
   }
 
   static jvmtiError SetLocalFloat(jvmtiEnv* env,
-                                  jthread thread ATTRIBUTE_UNUSED,
-                                  jint depth ATTRIBUTE_UNUSED,
-                                  jint slot ATTRIBUTE_UNUSED,
-                                  jfloat value ATTRIBUTE_UNUSED) {
+                                  jthread thread,
+                                  jint depth,
+                                  jint slot,
+                                  jfloat value) {
     ENSURE_VALID_ENV(env);
     ENSURE_HAS_CAP(env, can_access_local_variables);
-    return ERR(NOT_IMPLEMENTED);
+    return MethodUtil::SetLocalVariable(env, thread, depth, slot, value);
   }
 
   static jvmtiError SetLocalDouble(jvmtiEnv* env,
-                                   jthread thread ATTRIBUTE_UNUSED,
-                                   jint depth ATTRIBUTE_UNUSED,
-                                   jint slot ATTRIBUTE_UNUSED,
-                                   jdouble value ATTRIBUTE_UNUSED) {
+                                   jthread thread,
+                                   jint depth,
+                                   jint slot,
+                                   jdouble value) {
     ENSURE_VALID_ENV(env);
     ENSURE_HAS_CAP(env, can_access_local_variables);
-    return ERR(NOT_IMPLEMENTED);
+    return MethodUtil::SetLocalVariable(env, thread, depth, slot, value);
   }
 
 
@@ -904,12 +904,12 @@
   }
 
   static jvmtiError GetLocalVariableTable(jvmtiEnv* env,
-                                          jmethodID method ATTRIBUTE_UNUSED,
-                                          jint* entry_count_ptr ATTRIBUTE_UNUSED,
-                                          jvmtiLocalVariableEntry** table_ptr ATTRIBUTE_UNUSED) {
+                                          jmethodID method,
+                                          jint* entry_count_ptr,
+                                          jvmtiLocalVariableEntry** table_ptr) {
     ENSURE_VALID_ENV(env);
     ENSURE_HAS_CAP(env, can_access_local_variables);
-    return ERR(NOT_IMPLEMENTED);
+    return MethodUtil::GetLocalVariableTable(env, method, entry_count_ptr, table_ptr);
   }
 
   static jvmtiError GetBytecodes(jvmtiEnv* env,
diff --git a/runtime/openjdkjvmti/README.md b/openjdkjvmti/README.md
similarity index 100%
rename from runtime/openjdkjvmti/README.md
rename to openjdkjvmti/README.md
diff --git a/runtime/openjdkjvmti/art_jvmti.h b/openjdkjvmti/art_jvmti.h
similarity index 96%
rename from runtime/openjdkjvmti/art_jvmti.h
rename to openjdkjvmti/art_jvmti.h
index 4d5bb95..12f4cab 100644
--- a/runtime/openjdkjvmti/art_jvmti.h
+++ b/openjdkjvmti/art_jvmti.h
@@ -29,8 +29,8 @@
  * questions.
  */
 
-#ifndef ART_RUNTIME_OPENJDKJVMTI_ART_JVMTI_H_
-#define ART_RUNTIME_OPENJDKJVMTI_ART_JVMTI_H_
+#ifndef ART_OPENJDKJVMTI_ART_JVMTI_H_
+#define ART_OPENJDKJVMTI_ART_JVMTI_H_
 
 #include <memory>
 #include <type_traits>
@@ -204,6 +204,10 @@
 
 ALWAYS_INLINE
 static inline JvmtiUniquePtr<char[]> CopyString(jvmtiEnv* env, const char* src, jvmtiError* error) {
+  if (src == nullptr) {
+    JvmtiUniquePtr<char[]> ret = AllocJvmtiUniquePtr<char[]>(env, 0, error);
+    return ret;
+  }
   size_t len = strlen(src) + 1;
   JvmtiUniquePtr<char[]> ret = AllocJvmtiUniquePtr<char[]>(env, len, error);
   if (ret != nullptr) {
@@ -227,8 +231,8 @@
     .can_get_source_file_name                        = 1,
     .can_get_line_numbers                            = 1,
     .can_get_source_debug_extension                  = 1,
-    .can_access_local_variables                      = 0,
-    .can_maintain_original_method_order              = 0,
+    .can_access_local_variables                      = 1,
+    .can_maintain_original_method_order              = 1,
     .can_generate_single_step_events                 = 1,
     .can_generate_exception_events                   = 0,
     .can_generate_frame_pop_events                   = 0,
@@ -258,4 +262,4 @@
 
 }  // namespace openjdkjvmti
 
-#endif  // ART_RUNTIME_OPENJDKJVMTI_ART_JVMTI_H_
+#endif  // ART_OPENJDKJVMTI_ART_JVMTI_H_
diff --git a/runtime/openjdkjvmti/events-inl.h b/openjdkjvmti/events-inl.h
similarity index 97%
rename from runtime/openjdkjvmti/events-inl.h
rename to openjdkjvmti/events-inl.h
index 43177ab..32dba3e 100644
--- a/runtime/openjdkjvmti/events-inl.h
+++ b/openjdkjvmti/events-inl.h
@@ -14,8 +14,8 @@
  * limitations under the License.
  */
 
-#ifndef ART_RUNTIME_OPENJDKJVMTI_EVENTS_INL_H_
-#define ART_RUNTIME_OPENJDKJVMTI_EVENTS_INL_H_
+#ifndef ART_OPENJDKJVMTI_EVENTS_INL_H_
+#define ART_OPENJDKJVMTI_EVENTS_INL_H_
 
 #include <array>
 
@@ -414,9 +414,10 @@
                                            bool added) {
   ArtJvmtiEvent event = added ? ArtJvmtiEvent::kClassFileLoadHookNonRetransformable
                               : ArtJvmtiEvent::kClassFileLoadHookRetransformable;
-  return caps.can_retransform_classes == 1 &&
-      IsEventEnabledAnywhere(event) &&
-      env->event_masks.IsEnabledAnywhere(event);
+  return (added && caps.can_access_local_variables == 1) ||
+      (caps.can_retransform_classes == 1 &&
+       IsEventEnabledAnywhere(event) &&
+       env->event_masks.IsEnabledAnywhere(event));
 }
 
 inline void EventHandler::HandleChangedCapabilities(ArtJvmTiEnv* env,
@@ -428,9 +429,12 @@
       RecalculateGlobalEventMask(ArtJvmtiEvent::kClassFileLoadHookRetransformable);
       RecalculateGlobalEventMask(ArtJvmtiEvent::kClassFileLoadHookNonRetransformable);
     }
+    if (added && caps.can_access_local_variables == 1) {
+      HandleLocalAccessCapabilityAdded();
+    }
   }
 }
 
 }  // namespace openjdkjvmti
 
-#endif  // ART_RUNTIME_OPENJDKJVMTI_EVENTS_INL_H_
+#endif  // ART_OPENJDKJVMTI_EVENTS_INL_H_
diff --git a/runtime/openjdkjvmti/events.cc b/openjdkjvmti/events.cc
similarity index 96%
rename from runtime/openjdkjvmti/events.cc
rename to openjdkjvmti/events.cc
index 7a930d4..2944a45 100644
--- a/runtime/openjdkjvmti/events.cc
+++ b/openjdkjvmti/events.cc
@@ -610,6 +610,21 @@
   }
 }
 
+void EventHandler::HandleLocalAccessCapabilityAdded() {
+  art::ScopedThreadStateChange stsc(art::Thread::Current(), art::ThreadState::kNative);
+  art::instrumentation::Instrumentation* instr = art::Runtime::Current()->GetInstrumentation();
+  art::gc::ScopedGCCriticalSection gcs(art::Thread::Current(),
+                                       art::gc::kGcCauseInstrumentation,
+                                       art::gc::kCollectorTypeInstrumentation);
+  art::ScopedSuspendAll ssa("Deoptimize everything for local variable access", true);
+  // TODO This should be disabled when there are no environments using it.
+  if (!instr->CanDeoptimize()) {
+    instr->EnableDeoptimization();
+  }
+  // TODO We should be able to support can_access_local_variables without this.
+  instr->DeoptimizeEverything("jvmti-local-variable-access");
+}
+
 // Handle special work for the given event type, if necessary.
 void EventHandler::HandleEventType(ArtJvmtiEvent event, bool enable) {
   switch (event) {
diff --git a/runtime/openjdkjvmti/events.h b/openjdkjvmti/events.h
similarity index 98%
rename from runtime/openjdkjvmti/events.h
rename to openjdkjvmti/events.h
index 5f37dcf..3d05fa1 100644
--- a/runtime/openjdkjvmti/events.h
+++ b/openjdkjvmti/events.h
@@ -14,8 +14,8 @@
  * limitations under the License.
  */
 
-#ifndef ART_RUNTIME_OPENJDKJVMTI_EVENTS_H_
-#define ART_RUNTIME_OPENJDKJVMTI_EVENTS_H_
+#ifndef ART_OPENJDKJVMTI_EVENTS_H_
+#define ART_OPENJDKJVMTI_EVENTS_H_
 
 #include <bitset>
 #include <vector>
@@ -210,6 +210,7 @@
                                                            unsigned char** new_class_data) const;
 
   void HandleEventType(ArtJvmtiEvent event, bool enable);
+  void HandleLocalAccessCapabilityAdded();
 
   // List of all JvmTiEnv objects that have been created, in their creation order.
   // NB Some elements might be null representing envs that have been deleted. They should be skipped
@@ -226,4 +227,4 @@
 
 }  // namespace openjdkjvmti
 
-#endif  // ART_RUNTIME_OPENJDKJVMTI_EVENTS_H_
+#endif  // ART_OPENJDKJVMTI_EVENTS_H_
diff --git a/runtime/openjdkjvmti/fixed_up_dex_file.cc b/openjdkjvmti/fixed_up_dex_file.cc
similarity index 100%
rename from runtime/openjdkjvmti/fixed_up_dex_file.cc
rename to openjdkjvmti/fixed_up_dex_file.cc
diff --git a/runtime/openjdkjvmti/fixed_up_dex_file.h b/openjdkjvmti/fixed_up_dex_file.h
similarity index 94%
rename from runtime/openjdkjvmti/fixed_up_dex_file.h
rename to openjdkjvmti/fixed_up_dex_file.h
index a96ee12..4cb39cf 100644
--- a/runtime/openjdkjvmti/fixed_up_dex_file.h
+++ b/openjdkjvmti/fixed_up_dex_file.h
@@ -29,8 +29,8 @@
  * questions.
  */
 
-#ifndef ART_RUNTIME_OPENJDKJVMTI_FIXED_UP_DEX_FILE_H_
-#define ART_RUNTIME_OPENJDKJVMTI_FIXED_UP_DEX_FILE_H_
+#ifndef ART_OPENJDKJVMTI_FIXED_UP_DEX_FILE_H_
+#define ART_OPENJDKJVMTI_FIXED_UP_DEX_FILE_H_
 
 #include <memory>
 #include <vector>
@@ -80,4 +80,4 @@
 
 }  // namespace openjdkjvmti
 
-#endif  // ART_RUNTIME_OPENJDKJVMTI_FIXED_UP_DEX_FILE_H_
+#endif  // ART_OPENJDKJVMTI_FIXED_UP_DEX_FILE_H_
diff --git a/runtime/openjdkjvmti/include/jvmti.h b/openjdkjvmti/include/jvmti.h
similarity index 100%
rename from runtime/openjdkjvmti/include/jvmti.h
rename to openjdkjvmti/include/jvmti.h
diff --git a/runtime/openjdkjvmti/jvmti_allocator.h b/openjdkjvmti/jvmti_allocator.h
similarity index 96%
rename from runtime/openjdkjvmti/jvmti_allocator.h
rename to openjdkjvmti/jvmti_allocator.h
index 44b1cb1..e29e034 100644
--- a/runtime/openjdkjvmti/jvmti_allocator.h
+++ b/openjdkjvmti/jvmti_allocator.h
@@ -29,8 +29,8 @@
  * questions.
  */
 
-#ifndef ART_RUNTIME_OPENJDKJVMTI_JVMTI_ALLOCATOR_H_
-#define ART_RUNTIME_OPENJDKJVMTI_JVMTI_ALLOCATOR_H_
+#ifndef ART_OPENJDKJVMTI_JVMTI_ALLOCATOR_H_
+#define ART_OPENJDKJVMTI_JVMTI_ALLOCATOR_H_
 
 #include "base/logging.h"
 #include "base/macros.h"
@@ -171,4 +171,4 @@
 
 }  // namespace openjdkjvmti
 
-#endif  // ART_RUNTIME_OPENJDKJVMTI_JVMTI_ALLOCATOR_H_
+#endif  // ART_OPENJDKJVMTI_JVMTI_ALLOCATOR_H_
diff --git a/runtime/openjdkjvmti/jvmti_weak_table-inl.h b/openjdkjvmti/jvmti_weak_table-inl.h
similarity index 98%
rename from runtime/openjdkjvmti/jvmti_weak_table-inl.h
rename to openjdkjvmti/jvmti_weak_table-inl.h
index a640acb..1c82255 100644
--- a/runtime/openjdkjvmti/jvmti_weak_table-inl.h
+++ b/openjdkjvmti/jvmti_weak_table-inl.h
@@ -29,8 +29,8 @@
  * questions.
  */
 
-#ifndef ART_RUNTIME_OPENJDKJVMTI_JVMTI_WEAK_TABLE_INL_H_
-#define ART_RUNTIME_OPENJDKJVMTI_JVMTI_WEAK_TABLE_INL_H_
+#ifndef ART_OPENJDKJVMTI_JVMTI_WEAK_TABLE_INL_H_
+#define ART_OPENJDKJVMTI_JVMTI_WEAK_TABLE_INL_H_
 
 #include "jvmti_weak_table.h"
 
@@ -403,4 +403,4 @@
 
 }  // namespace openjdkjvmti
 
-#endif  // ART_RUNTIME_OPENJDKJVMTI_JVMTI_WEAK_TABLE_INL_H_
+#endif  // ART_OPENJDKJVMTI_JVMTI_WEAK_TABLE_INL_H_
diff --git a/runtime/openjdkjvmti/jvmti_weak_table.h b/openjdkjvmti/jvmti_weak_table.h
similarity index 97%
rename from runtime/openjdkjvmti/jvmti_weak_table.h
rename to openjdkjvmti/jvmti_weak_table.h
index a5175a4..5a821c9 100644
--- a/runtime/openjdkjvmti/jvmti_weak_table.h
+++ b/openjdkjvmti/jvmti_weak_table.h
@@ -29,8 +29,8 @@
  * questions.
  */
 
-#ifndef ART_RUNTIME_OPENJDKJVMTI_JVMTI_WEAK_TABLE_H_
-#define ART_RUNTIME_OPENJDKJVMTI_JVMTI_WEAK_TABLE_H_
+#ifndef ART_OPENJDKJVMTI_JVMTI_WEAK_TABLE_H_
+#define ART_OPENJDKJVMTI_JVMTI_WEAK_TABLE_H_
 
 #include <unordered_map>
 
@@ -224,4 +224,4 @@
 
 }  // namespace openjdkjvmti
 
-#endif  // ART_RUNTIME_OPENJDKJVMTI_JVMTI_WEAK_TABLE_H_
+#endif  // ART_OPENJDKJVMTI_JVMTI_WEAK_TABLE_H_
diff --git a/runtime/openjdkjvmti/object_tagging.cc b/openjdkjvmti/object_tagging.cc
similarity index 100%
rename from runtime/openjdkjvmti/object_tagging.cc
rename to openjdkjvmti/object_tagging.cc
diff --git a/runtime/openjdkjvmti/object_tagging.h b/openjdkjvmti/object_tagging.h
similarity index 94%
rename from runtime/openjdkjvmti/object_tagging.h
rename to openjdkjvmti/object_tagging.h
index ca84e44..b474845 100644
--- a/runtime/openjdkjvmti/object_tagging.h
+++ b/openjdkjvmti/object_tagging.h
@@ -29,8 +29,8 @@
  * questions.
  */
 
-#ifndef ART_RUNTIME_OPENJDKJVMTI_OBJECT_TAGGING_H_
-#define ART_RUNTIME_OPENJDKJVMTI_OBJECT_TAGGING_H_
+#ifndef ART_OPENJDKJVMTI_OBJECT_TAGGING_H_
+#define ART_OPENJDKJVMTI_OBJECT_TAGGING_H_
 
 #include <unordered_map>
 
@@ -83,4 +83,4 @@
 
 }  // namespace openjdkjvmti
 
-#endif  // ART_RUNTIME_OPENJDKJVMTI_OBJECT_TAGGING_H_
+#endif  // ART_OPENJDKJVMTI_OBJECT_TAGGING_H_
diff --git a/runtime/openjdkjvmti/ti_allocator.cc b/openjdkjvmti/ti_allocator.cc
similarity index 100%
rename from runtime/openjdkjvmti/ti_allocator.cc
rename to openjdkjvmti/ti_allocator.cc
diff --git a/runtime/openjdkjvmti/ti_allocator.h b/openjdkjvmti/ti_allocator.h
similarity index 93%
rename from runtime/openjdkjvmti/ti_allocator.h
rename to openjdkjvmti/ti_allocator.h
index 35575c3..776cc5e 100644
--- a/runtime/openjdkjvmti/ti_allocator.h
+++ b/openjdkjvmti/ti_allocator.h
@@ -29,8 +29,8 @@
  * questions.
  */
 
-#ifndef ART_RUNTIME_OPENJDKJVMTI_TI_ALLOCATOR_H_
-#define ART_RUNTIME_OPENJDKJVMTI_TI_ALLOCATOR_H_
+#ifndef ART_OPENJDKJVMTI_TI_ALLOCATOR_H_
+#define ART_OPENJDKJVMTI_TI_ALLOCATOR_H_
 
 #include "jni.h"
 #include "jvmti.h"
@@ -61,5 +61,5 @@
 
 }  // namespace openjdkjvmti
 
-#endif  // ART_RUNTIME_OPENJDKJVMTI_TI_ALLOCATOR_H_
+#endif  // ART_OPENJDKJVMTI_TI_ALLOCATOR_H_
 
diff --git a/runtime/openjdkjvmti/ti_breakpoint.cc b/openjdkjvmti/ti_breakpoint.cc
similarity index 100%
rename from runtime/openjdkjvmti/ti_breakpoint.cc
rename to openjdkjvmti/ti_breakpoint.cc
diff --git a/runtime/openjdkjvmti/ti_breakpoint.h b/openjdkjvmti/ti_breakpoint.h
similarity index 94%
rename from runtime/openjdkjvmti/ti_breakpoint.h
rename to openjdkjvmti/ti_breakpoint.h
index c3dbef7..9b08b42 100644
--- a/runtime/openjdkjvmti/ti_breakpoint.h
+++ b/openjdkjvmti/ti_breakpoint.h
@@ -29,8 +29,8 @@
  * questions.
  */
 
-#ifndef ART_RUNTIME_OPENJDKJVMTI_TI_BREAKPOINT_H_
-#define ART_RUNTIME_OPENJDKJVMTI_TI_BREAKPOINT_H_
+#ifndef ART_OPENJDKJVMTI_TI_BREAKPOINT_H_
+#define ART_OPENJDKJVMTI_TI_BREAKPOINT_H_
 
 #include "jni.h"
 #include "jvmti.h"
@@ -91,4 +91,4 @@
 };
 
 }  // namespace std
-#endif  // ART_RUNTIME_OPENJDKJVMTI_TI_BREAKPOINT_H_
+#endif  // ART_OPENJDKJVMTI_TI_BREAKPOINT_H_
diff --git a/runtime/openjdkjvmti/ti_class.cc b/openjdkjvmti/ti_class.cc
similarity index 100%
rename from runtime/openjdkjvmti/ti_class.cc
rename to openjdkjvmti/ti_class.cc
diff --git a/runtime/openjdkjvmti/ti_class.h b/openjdkjvmti/ti_class.h
similarity index 96%
rename from runtime/openjdkjvmti/ti_class.h
rename to openjdkjvmti/ti_class.h
index 7bb6b3e..dd99e36 100644
--- a/runtime/openjdkjvmti/ti_class.h
+++ b/openjdkjvmti/ti_class.h
@@ -29,8 +29,8 @@
  * questions.
  */
 
-#ifndef ART_RUNTIME_OPENJDKJVMTI_TI_CLASS_H_
-#define ART_RUNTIME_OPENJDKJVMTI_TI_CLASS_H_
+#ifndef ART_OPENJDKJVMTI_TI_CLASS_H_
+#define ART_OPENJDKJVMTI_TI_CLASS_H_
 
 #include "jni.h"
 #include "jvmti.h"
@@ -92,4 +92,4 @@
 
 }  // namespace openjdkjvmti
 
-#endif  // ART_RUNTIME_OPENJDKJVMTI_TI_CLASS_H_
+#endif  // ART_OPENJDKJVMTI_TI_CLASS_H_
diff --git a/runtime/openjdkjvmti/ti_class_definition.cc b/openjdkjvmti/ti_class_definition.cc
similarity index 100%
rename from runtime/openjdkjvmti/ti_class_definition.cc
rename to openjdkjvmti/ti_class_definition.cc
diff --git a/runtime/openjdkjvmti/ti_class_definition.h b/openjdkjvmti/ti_class_definition.h
similarity index 95%
rename from runtime/openjdkjvmti/ti_class_definition.h
rename to openjdkjvmti/ti_class_definition.h
index 2c268dd..accc456 100644
--- a/runtime/openjdkjvmti/ti_class_definition.h
+++ b/openjdkjvmti/ti_class_definition.h
@@ -29,8 +29,8 @@
  * questions.
  */
 
-#ifndef ART_RUNTIME_OPENJDKJVMTI_TI_CLASS_DEFINITION_H_
-#define ART_RUNTIME_OPENJDKJVMTI_TI_CLASS_DEFINITION_H_
+#ifndef ART_OPENJDKJVMTI_TI_CLASS_DEFINITION_H_
+#define ART_OPENJDKJVMTI_TI_CLASS_DEFINITION_H_
 
 #include "art_jvmti.h"
 
@@ -128,4 +128,4 @@
 
 }  // namespace openjdkjvmti
 
-#endif  // ART_RUNTIME_OPENJDKJVMTI_TI_CLASS_DEFINITION_H_
+#endif  // ART_OPENJDKJVMTI_TI_CLASS_DEFINITION_H_
diff --git a/runtime/openjdkjvmti/ti_class_loader.cc b/openjdkjvmti/ti_class_loader.cc
similarity index 100%
rename from runtime/openjdkjvmti/ti_class_loader.cc
rename to openjdkjvmti/ti_class_loader.cc
diff --git a/runtime/openjdkjvmti/ti_class_loader.h b/openjdkjvmti/ti_class_loader.h
similarity index 95%
rename from runtime/openjdkjvmti/ti_class_loader.h
rename to openjdkjvmti/ti_class_loader.h
index af66c5f..767e258 100644
--- a/runtime/openjdkjvmti/ti_class_loader.h
+++ b/openjdkjvmti/ti_class_loader.h
@@ -29,8 +29,8 @@
  * questions.
  */
 
-#ifndef ART_RUNTIME_OPENJDKJVMTI_TI_CLASS_LOADER_H_
-#define ART_RUNTIME_OPENJDKJVMTI_TI_CLASS_LOADER_H_
+#ifndef ART_OPENJDKJVMTI_TI_CLASS_LOADER_H_
+#define ART_OPENJDKJVMTI_TI_CLASS_LOADER_H_
 
 #include <string>
 
@@ -96,4 +96,4 @@
 };
 
 }  // namespace openjdkjvmti
-#endif  // ART_RUNTIME_OPENJDKJVMTI_TI_CLASS_LOADER_H_
+#endif  // ART_OPENJDKJVMTI_TI_CLASS_LOADER_H_
diff --git a/runtime/openjdkjvmti/ti_dump.cc b/openjdkjvmti/ti_dump.cc
similarity index 100%
rename from runtime/openjdkjvmti/ti_dump.cc
rename to openjdkjvmti/ti_dump.cc
diff --git a/runtime/openjdkjvmti/ti_dump.h b/openjdkjvmti/ti_dump.h
similarity index 92%
rename from runtime/openjdkjvmti/ti_dump.h
rename to openjdkjvmti/ti_dump.h
index 67cb239..323bf56 100644
--- a/runtime/openjdkjvmti/ti_dump.h
+++ b/openjdkjvmti/ti_dump.h
@@ -29,8 +29,8 @@
  * questions.
  */
 
-#ifndef ART_RUNTIME_OPENJDKJVMTI_TI_DUMP_H_
-#define ART_RUNTIME_OPENJDKJVMTI_TI_DUMP_H_
+#ifndef ART_OPENJDKJVMTI_TI_DUMP_H_
+#define ART_OPENJDKJVMTI_TI_DUMP_H_
 
 #include "jni.h"
 #include "jvmti.h"
@@ -47,4 +47,4 @@
 
 }  // namespace openjdkjvmti
 
-#endif  // ART_RUNTIME_OPENJDKJVMTI_TI_DUMP_H_
+#endif  // ART_OPENJDKJVMTI_TI_DUMP_H_
diff --git a/runtime/openjdkjvmti/ti_field.cc b/openjdkjvmti/ti_field.cc
similarity index 100%
rename from runtime/openjdkjvmti/ti_field.cc
rename to openjdkjvmti/ti_field.cc
diff --git a/runtime/openjdkjvmti/ti_field.h b/openjdkjvmti/ti_field.h
similarity index 95%
rename from runtime/openjdkjvmti/ti_field.h
rename to openjdkjvmti/ti_field.h
index 880949e..8a229ed 100644
--- a/runtime/openjdkjvmti/ti_field.h
+++ b/openjdkjvmti/ti_field.h
@@ -29,8 +29,8 @@
  * questions.
  */
 
-#ifndef ART_RUNTIME_OPENJDKJVMTI_TI_FIELD_H_
-#define ART_RUNTIME_OPENJDKJVMTI_TI_FIELD_H_
+#ifndef ART_OPENJDKJVMTI_TI_FIELD_H_
+#define ART_OPENJDKJVMTI_TI_FIELD_H_
 
 #include "jni.h"
 #include "jvmti.h"
@@ -69,4 +69,4 @@
 
 }  // namespace openjdkjvmti
 
-#endif  // ART_RUNTIME_OPENJDKJVMTI_TI_FIELD_H_
+#endif  // ART_OPENJDKJVMTI_TI_FIELD_H_
diff --git a/runtime/openjdkjvmti/ti_heap.cc b/openjdkjvmti/ti_heap.cc
similarity index 100%
rename from runtime/openjdkjvmti/ti_heap.cc
rename to openjdkjvmti/ti_heap.cc
diff --git a/runtime/openjdkjvmti/ti_heap.h b/openjdkjvmti/ti_heap.h
similarity index 94%
rename from runtime/openjdkjvmti/ti_heap.h
rename to openjdkjvmti/ti_heap.h
index 0c973db..62761b5 100644
--- a/runtime/openjdkjvmti/ti_heap.h
+++ b/openjdkjvmti/ti_heap.h
@@ -14,8 +14,8 @@
  * limitations under the License.
  */
 
-#ifndef ART_RUNTIME_OPENJDKJVMTI_TI_HEAP_H_
-#define ART_RUNTIME_OPENJDKJVMTI_TI_HEAP_H_
+#ifndef ART_OPENJDKJVMTI_TI_HEAP_H_
+#define ART_OPENJDKJVMTI_TI_HEAP_H_
 
 #include "jvmti.h"
 
@@ -70,4 +70,4 @@
 
 }  // namespace openjdkjvmti
 
-#endif  // ART_RUNTIME_OPENJDKJVMTI_TI_HEAP_H_
+#endif  // ART_OPENJDKJVMTI_TI_HEAP_H_
diff --git a/runtime/openjdkjvmti/ti_jni.cc b/openjdkjvmti/ti_jni.cc
similarity index 100%
rename from runtime/openjdkjvmti/ti_jni.cc
rename to openjdkjvmti/ti_jni.cc
diff --git a/runtime/openjdkjvmti/ti_jni.h b/openjdkjvmti/ti_jni.h
similarity index 94%
rename from runtime/openjdkjvmti/ti_jni.h
rename to openjdkjvmti/ti_jni.h
index 906aab0..590fd54 100644
--- a/runtime/openjdkjvmti/ti_jni.h
+++ b/openjdkjvmti/ti_jni.h
@@ -29,8 +29,8 @@
  * questions.
  */
 
-#ifndef ART_RUNTIME_OPENJDKJVMTI_TI_JNI_H_
-#define ART_RUNTIME_OPENJDKJVMTI_TI_JNI_H_
+#ifndef ART_OPENJDKJVMTI_TI_JNI_H_
+#define ART_OPENJDKJVMTI_TI_JNI_H_
 
 #include "jni.h"
 #include "jvmti.h"
@@ -55,4 +55,4 @@
 
 }  // namespace openjdkjvmti
 
-#endif  // ART_RUNTIME_OPENJDKJVMTI_TI_JNI_H_
+#endif  // ART_OPENJDKJVMTI_TI_JNI_H_
diff --git a/openjdkjvmti/ti_method.cc b/openjdkjvmti/ti_method.cc
new file mode 100644
index 0000000..8f72714
--- /dev/null
+++ b/openjdkjvmti/ti_method.cc
@@ -0,0 +1,1071 @@
+/* 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_method.h"
+
+#include "art_jvmti.h"
+#include "art_method-inl.h"
+#include "base/enums.h"
+#include "base/mutex-inl.h"
+#include "dex_file_annotations.h"
+#include "events-inl.h"
+#include "jni_internal.h"
+#include "mirror/class-inl.h"
+#include "mirror/class_loader.h"
+#include "mirror/object-inl.h"
+#include "mirror/object_array-inl.h"
+#include "modifiers.h"
+#include "nativehelper/ScopedLocalRef.h"
+#include "runtime_callbacks.h"
+#include "scoped_thread_state_change-inl.h"
+#include "stack.h"
+#include "thread-current-inl.h"
+#include "thread_list.h"
+#include "ti_thread.h"
+#include "ti_phase.h"
+
+namespace openjdkjvmti {
+
+struct TiMethodCallback : public art::MethodCallback {
+  void RegisterNativeMethod(art::ArtMethod* method,
+                            const void* cur_method,
+                            /*out*/void** new_method)
+      OVERRIDE REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    if (event_handler->IsEventEnabledAnywhere(ArtJvmtiEvent::kNativeMethodBind)) {
+      art::Thread* thread = art::Thread::Current();
+      art::JNIEnvExt* jnienv = thread->GetJniEnv();
+      ScopedLocalRef<jthread> thread_jni(
+          jnienv, PhaseUtil::IsLivePhase() ? jnienv->AddLocalReference<jthread>(thread->GetPeer())
+                                           : nullptr);
+      art::ScopedThreadSuspension sts(thread, art::ThreadState::kNative);
+      event_handler->DispatchEvent<ArtJvmtiEvent::kNativeMethodBind>(
+          thread,
+          static_cast<JNIEnv*>(jnienv),
+          thread_jni.get(),
+          art::jni::EncodeArtMethod(method),
+          const_cast<void*>(cur_method),
+          new_method);
+    }
+  }
+
+  EventHandler* event_handler = nullptr;
+};
+
+TiMethodCallback gMethodCallback;
+
+void MethodUtil::Register(EventHandler* handler) {
+  gMethodCallback.event_handler = handler;
+  art::ScopedThreadStateChange stsc(art::Thread::Current(),
+                                    art::ThreadState::kWaitingForDebuggerToAttach);
+  art::ScopedSuspendAll ssa("Add method callback");
+  art::Runtime::Current()->GetRuntimeCallbacks()->AddMethodCallback(&gMethodCallback);
+}
+
+void MethodUtil::Unregister() {
+  art::ScopedThreadStateChange stsc(art::Thread::Current(),
+                                    art::ThreadState::kWaitingForDebuggerToAttach);
+  art::ScopedSuspendAll ssa("Remove method callback");
+  art::Runtime* runtime = art::Runtime::Current();
+  runtime->GetRuntimeCallbacks()->RemoveMethodCallback(&gMethodCallback);
+}
+
+jvmtiError MethodUtil::GetBytecodes(jvmtiEnv* env,
+                                    jmethodID method,
+                                    jint* size_ptr,
+                                    unsigned char** bytecode_ptr) {
+  if (method == nullptr) {
+    return ERR(INVALID_METHODID);
+  }
+  art::ArtMethod* art_method = art::jni::DecodeArtMethod(method);
+
+  if (art_method->IsNative()) {
+    return ERR(NATIVE_METHOD);
+  }
+
+  if (size_ptr == nullptr || bytecode_ptr == nullptr) {
+    return ERR(NULL_POINTER);
+  }
+
+  art::ScopedObjectAccess soa(art::Thread::Current());
+  const art::DexFile::CodeItem* code_item = art_method->GetCodeItem();
+  if (code_item == nullptr) {
+    *size_ptr = 0;
+    *bytecode_ptr = nullptr;
+    return OK;
+  }
+  // 2 bytes per instruction for dex code.
+  *size_ptr = code_item->insns_size_in_code_units_ * 2;
+  jvmtiError err = env->Allocate(*size_ptr, bytecode_ptr);
+  if (err != OK) {
+    return err;
+  }
+  memcpy(*bytecode_ptr, code_item->insns_, *size_ptr);
+  return OK;
+}
+
+jvmtiError MethodUtil::GetArgumentsSize(jvmtiEnv* env ATTRIBUTE_UNUSED,
+                                        jmethodID method,
+                                        jint* size_ptr) {
+  if (method == nullptr) {
+    return ERR(INVALID_METHODID);
+  }
+  art::ArtMethod* art_method = art::jni::DecodeArtMethod(method);
+
+  if (art_method->IsNative()) {
+    return ERR(NATIVE_METHOD);
+  }
+
+  if (size_ptr == nullptr) {
+    return ERR(NULL_POINTER);
+  }
+
+  art::ScopedObjectAccess soa(art::Thread::Current());
+  if (art_method->IsProxyMethod() || art_method->IsAbstract()) {
+    // Use the shorty.
+    art::ArtMethod* base_method = art_method->GetInterfaceMethodIfProxy(art::kRuntimePointerSize);
+    size_t arg_count = art::ArtMethod::NumArgRegisters(base_method->GetShorty());
+    if (!base_method->IsStatic()) {
+      arg_count++;
+    }
+    *size_ptr = static_cast<jint>(arg_count);
+    return ERR(NONE);
+  }
+
+  DCHECK_NE(art_method->GetCodeItemOffset(), 0u);
+  *size_ptr = art_method->GetCodeItem()->ins_size_;
+
+  return ERR(NONE);
+}
+
+jvmtiError MethodUtil::GetLocalVariableTable(jvmtiEnv* env,
+                                             jmethodID method,
+                                             jint* entry_count_ptr,
+                                             jvmtiLocalVariableEntry** table_ptr) {
+  if (method == nullptr) {
+    return ERR(INVALID_METHODID);
+  }
+  art::ArtMethod* art_method = art::jni::DecodeArtMethod(method);
+
+  if (art_method->IsNative()) {
+    return ERR(NATIVE_METHOD);
+  }
+
+  if (entry_count_ptr == nullptr || table_ptr == nullptr) {
+    return ERR(NULL_POINTER);
+  }
+
+  art::ScopedObjectAccess soa(art::Thread::Current());
+  const art::DexFile* dex_file = art_method->GetDexFile();
+  const art::DexFile::CodeItem* code_item = art_method->GetCodeItem();
+  // TODO code_item == nullptr means that the method is abstract (or native, but we check that
+  // earlier). We should check what is returned by the RI in this situation since it's not clear
+  // what the appropriate return value is from the spec.
+  if (dex_file == nullptr || code_item == nullptr) {
+    return ERR(ABSENT_INFORMATION);
+  }
+
+  struct LocalVariableContext {
+    explicit LocalVariableContext(jvmtiEnv* jenv) : env_(jenv), variables_(), err_(OK) {}
+
+    static void Callback(void* raw_ctx, const art::DexFile::LocalInfo& entry) {
+      reinterpret_cast<LocalVariableContext*>(raw_ctx)->Insert(entry);
+    }
+
+    void Insert(const art::DexFile::LocalInfo& entry) {
+      if (err_ != OK) {
+        return;
+      }
+      JvmtiUniquePtr<char[]> name_str = CopyString(env_, entry.name_, &err_);
+      if (err_ != OK) {
+        return;
+      }
+      JvmtiUniquePtr<char[]> sig_str = CopyString(env_, entry.descriptor_, &err_);
+      if (err_ != OK) {
+        return;
+      }
+      JvmtiUniquePtr<char[]> generic_sig_str = CopyString(env_, entry.signature_, &err_);
+      if (err_ != OK) {
+        return;
+      }
+      variables_.push_back({
+        .start_location = static_cast<jlocation>(entry.start_address_),
+        .length = static_cast<jint>(entry.end_address_ - entry.start_address_),
+        .name = name_str.release(),
+        .signature = sig_str.release(),
+        .generic_signature = generic_sig_str.release(),
+        .slot = entry.reg_,
+      });
+    }
+
+    jvmtiError Release(jint* out_entry_count_ptr, jvmtiLocalVariableEntry** out_table_ptr) {
+      jlong table_size = sizeof(jvmtiLocalVariableEntry) * variables_.size();
+      if (err_ != OK ||
+          (err_ = env_->Allocate(table_size,
+                                 reinterpret_cast<unsigned char**>(out_table_ptr))) != OK) {
+        Cleanup();
+        return err_;
+      } else {
+        *out_entry_count_ptr = variables_.size();
+        memcpy(*out_table_ptr, variables_.data(), table_size);
+        return OK;
+      }
+    }
+
+    void Cleanup() {
+      for (jvmtiLocalVariableEntry& e : variables_) {
+        env_->Deallocate(reinterpret_cast<unsigned char*>(e.name));
+        env_->Deallocate(reinterpret_cast<unsigned char*>(e.signature));
+        env_->Deallocate(reinterpret_cast<unsigned char*>(e.generic_signature));
+      }
+    }
+
+    jvmtiEnv* env_;
+    std::vector<jvmtiLocalVariableEntry> variables_;
+    jvmtiError err_;
+  };
+
+  LocalVariableContext context(env);
+  if (!dex_file->DecodeDebugLocalInfo(code_item,
+                                      art_method->IsStatic(),
+                                      art_method->GetDexMethodIndex(),
+                                      LocalVariableContext::Callback,
+                                      &context)) {
+    // Something went wrong with decoding the debug information. It might as well not be there.
+    return ERR(ABSENT_INFORMATION);
+  } else {
+    return context.Release(entry_count_ptr, table_ptr);
+  }
+}
+
+jvmtiError MethodUtil::GetMaxLocals(jvmtiEnv* env ATTRIBUTE_UNUSED,
+                                    jmethodID method,
+                                    jint* max_ptr) {
+  if (method == nullptr) {
+    return ERR(INVALID_METHODID);
+  }
+  art::ArtMethod* art_method = art::jni::DecodeArtMethod(method);
+
+  if (art_method->IsNative()) {
+    return ERR(NATIVE_METHOD);
+  }
+
+  if (max_ptr == nullptr) {
+    return ERR(NULL_POINTER);
+  }
+
+  art::ScopedObjectAccess soa(art::Thread::Current());
+  if (art_method->IsProxyMethod() || art_method->IsAbstract()) {
+    // This isn't specified as an error case, so return 0.
+    *max_ptr = 0;
+    return ERR(NONE);
+  }
+
+  DCHECK_NE(art_method->GetCodeItemOffset(), 0u);
+  *max_ptr = art_method->GetCodeItem()->registers_size_;
+
+  return ERR(NONE);
+}
+
+jvmtiError MethodUtil::GetMethodName(jvmtiEnv* env,
+                                     jmethodID method,
+                                     char** name_ptr,
+                                     char** signature_ptr,
+                                     char** generic_ptr) {
+  art::ScopedObjectAccess soa(art::Thread::Current());
+  art::ArtMethod* art_method = art::jni::DecodeArtMethod(method);
+  art_method = art_method->GetInterfaceMethodIfProxy(art::kRuntimePointerSize);
+
+  JvmtiUniquePtr<char[]> name_copy;
+  if (name_ptr != nullptr) {
+    const char* method_name = art_method->GetName();
+    if (method_name == nullptr) {
+      method_name = "<error>";
+    }
+    jvmtiError ret;
+    name_copy = CopyString(env, method_name, &ret);
+    if (name_copy == nullptr) {
+      return ret;
+    }
+    *name_ptr = name_copy.get();
+  }
+
+  JvmtiUniquePtr<char[]> signature_copy;
+  if (signature_ptr != nullptr) {
+    const art::Signature sig = art_method->GetSignature();
+    std::string str = sig.ToString();
+    jvmtiError ret;
+    signature_copy = CopyString(env, str.c_str(), &ret);
+    if (signature_copy == nullptr) {
+      return ret;
+    }
+    *signature_ptr = signature_copy.get();
+  }
+
+  if (generic_ptr != nullptr) {
+    *generic_ptr = nullptr;
+    if (!art_method->GetDeclaringClass()->IsProxyClass()) {
+      art::mirror::ObjectArray<art::mirror::String>* str_array =
+          art::annotations::GetSignatureAnnotationForMethod(art_method);
+      if (str_array != nullptr) {
+        std::ostringstream oss;
+        for (int32_t i = 0; i != str_array->GetLength(); ++i) {
+          oss << str_array->Get(i)->ToModifiedUtf8();
+        }
+        std::string output_string = oss.str();
+        jvmtiError ret;
+        JvmtiUniquePtr<char[]> generic_copy = CopyString(env, output_string.c_str(), &ret);
+        if (generic_copy == nullptr) {
+          return ret;
+        }
+        *generic_ptr = generic_copy.release();
+      } else if (soa.Self()->IsExceptionPending()) {
+        // TODO: Should we report an error here?
+        soa.Self()->ClearException();
+      }
+    }
+  }
+
+  // Everything is fine, release the buffers.
+  name_copy.release();
+  signature_copy.release();
+
+  return ERR(NONE);
+}
+
+jvmtiError MethodUtil::GetMethodDeclaringClass(jvmtiEnv* env ATTRIBUTE_UNUSED,
+                                               jmethodID method,
+                                               jclass* declaring_class_ptr) {
+  if (declaring_class_ptr == nullptr) {
+    return ERR(NULL_POINTER);
+  }
+
+  art::ArtMethod* art_method = art::jni::DecodeArtMethod(method);
+  // Note: No GetInterfaceMethodIfProxy, we want to actual class.
+
+  art::ScopedObjectAccess soa(art::Thread::Current());
+  art::mirror::Class* klass = art_method->GetDeclaringClass();
+  *declaring_class_ptr = soa.AddLocalReference<jclass>(klass);
+
+  return ERR(NONE);
+}
+
+jvmtiError MethodUtil::GetMethodLocation(jvmtiEnv* env ATTRIBUTE_UNUSED,
+                                         jmethodID method,
+                                         jlocation* start_location_ptr,
+                                         jlocation* end_location_ptr) {
+  if (method == nullptr) {
+    return ERR(INVALID_METHODID);
+  }
+  art::ArtMethod* art_method = art::jni::DecodeArtMethod(method);
+
+  if (art_method->IsNative()) {
+    return ERR(NATIVE_METHOD);
+  }
+
+  if (start_location_ptr == nullptr || end_location_ptr == nullptr) {
+    return ERR(NULL_POINTER);
+  }
+
+  art::ScopedObjectAccess soa(art::Thread::Current());
+  if (art_method->IsProxyMethod() || art_method->IsAbstract()) {
+    // This isn't specified as an error case, so return -1/-1 as the RI does.
+    *start_location_ptr = -1;
+    *end_location_ptr = -1;
+    return ERR(NONE);
+  }
+
+  DCHECK_NE(art_method->GetCodeItemOffset(), 0u);
+  *start_location_ptr = 0;
+  *end_location_ptr = art_method->GetCodeItem()->insns_size_in_code_units_ - 1;
+
+  return ERR(NONE);
+}
+
+jvmtiError MethodUtil::GetMethodModifiers(jvmtiEnv* env ATTRIBUTE_UNUSED,
+                                          jmethodID method,
+                                          jint* modifiers_ptr) {
+  if (modifiers_ptr == nullptr) {
+    return ERR(NULL_POINTER);
+  }
+
+  art::ArtMethod* art_method = art::jni::DecodeArtMethod(method);
+  uint32_t modifiers = art_method->GetAccessFlags();
+
+  // Note: Keep this code in sync with Executable.fixMethodFlags.
+  if ((modifiers & art::kAccAbstract) != 0) {
+    modifiers &= ~art::kAccNative;
+  }
+  modifiers &= ~art::kAccSynchronized;
+  if ((modifiers & art::kAccDeclaredSynchronized) != 0) {
+    modifiers |= art::kAccSynchronized;
+  }
+  modifiers &= art::kAccJavaFlagsMask;
+
+  *modifiers_ptr = modifiers;
+  return ERR(NONE);
+}
+
+using LineNumberContext = std::vector<jvmtiLineNumberEntry>;
+
+static bool CollectLineNumbers(void* void_context, const art::DexFile::PositionInfo& entry) {
+  LineNumberContext* context = reinterpret_cast<LineNumberContext*>(void_context);
+  jvmtiLineNumberEntry jvmti_entry = { static_cast<jlocation>(entry.address_),
+                                       static_cast<jint>(entry.line_) };
+  context->push_back(jvmti_entry);
+  return false;  // Collect all, no early exit.
+}
+
+jvmtiError MethodUtil::GetLineNumberTable(jvmtiEnv* env,
+                                          jmethodID method,
+                                          jint* entry_count_ptr,
+                                          jvmtiLineNumberEntry** table_ptr) {
+  if (method == nullptr) {
+    return ERR(NULL_POINTER);
+  }
+  art::ArtMethod* art_method = art::jni::DecodeArtMethod(method);
+  DCHECK(!art_method->IsRuntimeMethod());
+
+  const art::DexFile::CodeItem* code_item;
+  const art::DexFile* dex_file;
+  {
+    art::ScopedObjectAccess soa(art::Thread::Current());
+
+    if (art_method->IsProxyMethod()) {
+      return ERR(ABSENT_INFORMATION);
+    }
+    if (art_method->IsNative()) {
+      return ERR(NATIVE_METHOD);
+    }
+    if (entry_count_ptr == nullptr || table_ptr == nullptr) {
+      return ERR(NULL_POINTER);
+    }
+
+    code_item = art_method->GetCodeItem();
+    dex_file = art_method->GetDexFile();
+    DCHECK(code_item != nullptr) << art_method->PrettyMethod() << " " << dex_file->GetLocation();
+  }
+
+  LineNumberContext context;
+  bool success = dex_file->DecodeDebugPositionInfo(code_item, CollectLineNumbers, &context);
+  if (!success) {
+    return ERR(ABSENT_INFORMATION);
+  }
+
+  unsigned char* data;
+  jlong mem_size = context.size() * sizeof(jvmtiLineNumberEntry);
+  jvmtiError alloc_error = env->Allocate(mem_size, &data);
+  if (alloc_error != ERR(NONE)) {
+    return alloc_error;
+  }
+  *table_ptr = reinterpret_cast<jvmtiLineNumberEntry*>(data);
+  memcpy(*table_ptr, context.data(), mem_size);
+  *entry_count_ptr = static_cast<jint>(context.size());
+
+  return ERR(NONE);
+}
+
+template <typename T>
+static jvmtiError IsMethodT(jvmtiEnv* env ATTRIBUTE_UNUSED,
+                            jmethodID method,
+                            T test,
+                            jboolean* is_t_ptr) {
+  if (method == nullptr) {
+    return ERR(INVALID_METHODID);
+  }
+  if (is_t_ptr == nullptr) {
+    return ERR(NULL_POINTER);
+  }
+
+  art::ArtMethod* art_method = art::jni::DecodeArtMethod(method);
+  *is_t_ptr = test(art_method) ? JNI_TRUE : JNI_FALSE;
+
+  return ERR(NONE);
+}
+
+jvmtiError MethodUtil::IsMethodNative(jvmtiEnv* env, jmethodID m, jboolean* is_native_ptr) {
+  auto test = [](art::ArtMethod* method) {
+    return method->IsNative();
+  };
+  return IsMethodT(env, m, test, is_native_ptr);
+}
+
+jvmtiError MethodUtil::IsMethodObsolete(jvmtiEnv* env, jmethodID m, jboolean* is_obsolete_ptr) {
+  auto test = [](art::ArtMethod* method) {
+    return method->IsObsolete();
+  };
+  return IsMethodT(env, m, test, is_obsolete_ptr);
+}
+
+jvmtiError MethodUtil::IsMethodSynthetic(jvmtiEnv* env, jmethodID m, jboolean* is_synthetic_ptr) {
+  auto test = [](art::ArtMethod* method) {
+    return method->IsSynthetic();
+  };
+  return IsMethodT(env, m, test, is_synthetic_ptr);
+}
+
+struct FindFrameAtDepthVisitor : art::StackVisitor {
+ public:
+  FindFrameAtDepthVisitor(art::Thread* target, art::Context* ctx, jint depth)
+      REQUIRES_SHARED(art::Locks::mutator_lock_)
+      : art::StackVisitor(target, ctx, art::StackVisitor::StackWalkKind::kIncludeInlinedFrames),
+        found_frame_(false),
+        cnt_(0),
+        depth_(static_cast<size_t>(depth)) { }
+
+  bool FoundFrame() {
+    return found_frame_;
+  }
+
+  bool VisitFrame() NO_THREAD_SAFETY_ANALYSIS {
+    if (GetMethod()->IsRuntimeMethod()) {
+      return true;
+    }
+    if (cnt_ == depth_) {
+      // We found our frame, exit.
+      found_frame_ = true;
+      return false;
+    } else {
+      cnt_++;
+      return true;
+    }
+  }
+
+ private:
+  bool found_frame_;
+  size_t cnt_;
+  size_t depth_;
+};
+
+class CommonLocalVariableClosure : public art::Closure {
+ public:
+  CommonLocalVariableClosure(art::Thread* caller,
+                             jint depth,
+                             jint slot)
+      : result_(ERR(INTERNAL)), caller_(caller), depth_(depth), slot_(slot) {}
+
+  void Run(art::Thread* self) OVERRIDE REQUIRES(art::Locks::mutator_lock_) {
+    art::Locks::mutator_lock_->AssertSharedHeld(art::Thread::Current());
+    std::unique_ptr<art::Context> context(art::Context::Create());
+    FindFrameAtDepthVisitor visitor(self, context.get(), depth_);
+    visitor.WalkStack();
+    if (!visitor.FoundFrame()) {
+      // Must have been a bad depth.
+      result_ = ERR(NO_MORE_FRAMES);
+      return;
+    }
+    art::ArtMethod* method = visitor.GetMethod();
+    if (method->IsNative() || !visitor.IsShadowFrame()) {
+      // TODO We really should support get/set for non-shadow frames.
+      result_ = ERR(OPAQUE_FRAME);
+      return;
+    } else if (method->GetCodeItem()->registers_size_ <= slot_) {
+      result_ = ERR(INVALID_SLOT);
+      return;
+    }
+    uint32_t pc = visitor.GetDexPc(/*abort_on_failure*/ false);
+    if (pc == art::DexFile::kDexNoIndex) {
+      // Cannot figure out current PC.
+      result_ = ERR(OPAQUE_FRAME);
+      return;
+    }
+    std::string descriptor;
+    art::Primitive::Type slot_type = art::Primitive::kPrimVoid;
+    jvmtiError err = GetSlotType(method, pc, &descriptor, &slot_type);
+    if (err != OK) {
+      result_ = err;
+      return;
+    }
+
+    err = GetTypeError(method, slot_type, descriptor);
+    if (err != OK) {
+      result_ = err;
+      return;
+    }
+    result_ = Execute(method, visitor);
+  }
+
+  jvmtiError GetResult() const {
+    return result_;
+  }
+
+ protected:
+  virtual jvmtiError Execute(art::ArtMethod* method, art::StackVisitor& visitor)
+      REQUIRES(art::Locks::mutator_lock_) = 0;
+  virtual jvmtiError GetTypeError(art::ArtMethod* method,
+                                  art::Primitive::Type type,
+                                  const std::string& descriptor)
+      REQUIRES(art::Locks::mutator_lock_)  = 0;
+
+  jvmtiError GetSlotType(art::ArtMethod* method,
+                         uint32_t dex_pc,
+                         /*out*/std::string* descriptor,
+                         /*out*/art::Primitive::Type* type)
+      REQUIRES(art::Locks::mutator_lock_) {
+    const art::DexFile* dex_file = method->GetDexFile();
+    const art::DexFile::CodeItem* code_item = method->GetCodeItem();
+    if (dex_file == nullptr || code_item == nullptr) {
+      return ERR(OPAQUE_FRAME);
+    }
+
+    struct GetLocalVariableInfoContext {
+      explicit GetLocalVariableInfoContext(jint slot,
+                                          uint32_t pc,
+                                          std::string* out_descriptor,
+                                          art::Primitive::Type* out_type)
+          : found_(false), jslot_(slot), pc_(pc), descriptor_(out_descriptor), type_(out_type) {
+        *descriptor_ = "";
+        *type_ = art::Primitive::kPrimVoid;
+      }
+
+      static void Callback(void* raw_ctx, const art::DexFile::LocalInfo& entry) {
+        reinterpret_cast<GetLocalVariableInfoContext*>(raw_ctx)->Handle(entry);
+      }
+
+      void Handle(const art::DexFile::LocalInfo& entry) {
+        if (found_) {
+          return;
+        } else if (entry.start_address_ <= pc_ &&
+                   entry.end_address_ > pc_ &&
+                   entry.reg_ == jslot_) {
+          found_ = true;
+          *type_ = art::Primitive::GetType(entry.descriptor_[0]);
+          *descriptor_ = entry.descriptor_;
+        }
+        return;
+      }
+
+      bool found_;
+      jint jslot_;
+      uint32_t pc_;
+      std::string* descriptor_;
+      art::Primitive::Type* type_;
+    };
+
+    GetLocalVariableInfoContext context(slot_, dex_pc, descriptor, type);
+    if (!dex_file->DecodeDebugLocalInfo(code_item,
+                                        method->IsStatic(),
+                                        method->GetDexMethodIndex(),
+                                        GetLocalVariableInfoContext::Callback,
+                                        &context) || !context.found_) {
+      // Something went wrong with decoding the debug information. It might as well not be there.
+      return ERR(INVALID_SLOT);
+    } else {
+      return OK;
+    }
+  }
+
+  jvmtiError result_;
+  art::Thread* caller_;
+  jint depth_;
+  jint slot_;
+};
+
+class GetLocalVariableClosure : public CommonLocalVariableClosure {
+ public:
+  GetLocalVariableClosure(art::Thread* caller,
+                          jint depth,
+                          jint slot,
+                          art::Primitive::Type type,
+                          jvalue* val)
+      : CommonLocalVariableClosure(caller, depth, slot), type_(type), val_(val) {}
+
+ protected:
+  jvmtiError GetTypeError(art::ArtMethod* method ATTRIBUTE_UNUSED,
+                          art::Primitive::Type slot_type,
+                          const std::string& descriptor ATTRIBUTE_UNUSED)
+      OVERRIDE REQUIRES(art::Locks::mutator_lock_) {
+    switch (slot_type) {
+      case art::Primitive::kPrimByte:
+      case art::Primitive::kPrimChar:
+      case art::Primitive::kPrimInt:
+      case art::Primitive::kPrimShort:
+      case art::Primitive::kPrimBoolean:
+        return type_ == art::Primitive::kPrimInt ? OK : ERR(TYPE_MISMATCH);
+      case art::Primitive::kPrimLong:
+      case art::Primitive::kPrimFloat:
+      case art::Primitive::kPrimDouble:
+      case art::Primitive::kPrimNot:
+        return type_ == slot_type ? OK : ERR(TYPE_MISMATCH);
+      case art::Primitive::kPrimVoid:
+        LOG(FATAL) << "Unexpected primitive type " << slot_type;
+        UNREACHABLE();
+    }
+  }
+
+  jvmtiError Execute(art::ArtMethod* method, art::StackVisitor& visitor)
+      OVERRIDE REQUIRES(art::Locks::mutator_lock_) {
+    switch (type_) {
+      case art::Primitive::kPrimNot: {
+        uint32_t ptr_val;
+        if (!visitor.GetVReg(method,
+                             static_cast<uint16_t>(slot_),
+                             art::kReferenceVReg,
+                             &ptr_val)) {
+          return ERR(OPAQUE_FRAME);
+        }
+        art::ObjPtr<art::mirror::Object> obj(reinterpret_cast<art::mirror::Object*>(ptr_val));
+        val_->l = obj.IsNull() ? nullptr : caller_->GetJniEnv()->AddLocalReference<jobject>(obj);
+        break;
+      }
+      case art::Primitive::kPrimInt:
+      case art::Primitive::kPrimFloat: {
+        if (!visitor.GetVReg(method,
+                             static_cast<uint16_t>(slot_),
+                             type_ == art::Primitive::kPrimFloat ? art::kFloatVReg : art::kIntVReg,
+                             reinterpret_cast<uint32_t*>(&val_->i))) {
+          return ERR(OPAQUE_FRAME);
+        }
+        break;
+      }
+      case art::Primitive::kPrimDouble:
+      case art::Primitive::kPrimLong: {
+        auto lo_type = type_ == art::Primitive::kPrimLong ? art::kLongLoVReg : art::kDoubleLoVReg;
+        auto high_type = type_ == art::Primitive::kPrimLong ? art::kLongHiVReg : art::kDoubleHiVReg;
+        if (!visitor.GetVRegPair(method,
+                                 static_cast<uint16_t>(slot_),
+                                 lo_type,
+                                 high_type,
+                                 reinterpret_cast<uint64_t*>(&val_->j))) {
+          return ERR(OPAQUE_FRAME);
+        }
+        break;
+      }
+      default: {
+        LOG(FATAL) << "unexpected register type " << type_;
+        UNREACHABLE();
+      }
+    }
+    return OK;
+  }
+
+ private:
+  art::Primitive::Type type_;
+  jvalue* val_;
+};
+
+jvmtiError MethodUtil::GetLocalVariableGeneric(jvmtiEnv* env ATTRIBUTE_UNUSED,
+                                               jthread thread,
+                                               jint depth,
+                                               jint slot,
+                                               art::Primitive::Type type,
+                                               jvalue* val) {
+  if (depth < 0) {
+    return ERR(ILLEGAL_ARGUMENT);
+  }
+  art::Thread* self = art::Thread::Current();
+  art::ScopedObjectAccess soa(self);
+  art::MutexLock mu(self, *art::Locks::thread_list_lock_);
+  art::Thread* target = ThreadUtil::GetNativeThread(thread, soa);
+  if (target == nullptr && thread == nullptr) {
+    return ERR(INVALID_THREAD);
+  }
+  if (target == nullptr) {
+    return ERR(THREAD_NOT_ALIVE);
+  }
+  GetLocalVariableClosure c(self, depth, slot, type, val);
+  if (!target->RequestSynchronousCheckpoint(&c)) {
+    return ERR(THREAD_NOT_ALIVE);
+  } else {
+    return c.GetResult();
+  }
+}
+
+class SetLocalVariableClosure : public CommonLocalVariableClosure {
+ public:
+  SetLocalVariableClosure(art::Thread* caller,
+                          jint depth,
+                          jint slot,
+                          art::Primitive::Type type,
+                          jvalue val)
+      : CommonLocalVariableClosure(caller, depth, slot), type_(type), val_(val) {}
+
+ protected:
+  jvmtiError GetTypeError(art::ArtMethod* method,
+                          art::Primitive::Type slot_type,
+                          const std::string& descriptor)
+      OVERRIDE REQUIRES(art::Locks::mutator_lock_) {
+    switch (slot_type) {
+      case art::Primitive::kPrimNot: {
+        if (type_ != art::Primitive::kPrimNot) {
+          return ERR(TYPE_MISMATCH);
+        } else if (val_.l == nullptr) {
+          return OK;
+        } else {
+          art::ClassLinker* cl = art::Runtime::Current()->GetClassLinker();
+          art::ObjPtr<art::mirror::Class> set_class =
+              caller_->DecodeJObject(val_.l)->GetClass();
+          art::ObjPtr<art::mirror::ClassLoader> loader =
+              method->GetDeclaringClass()->GetClassLoader();
+          art::ObjPtr<art::mirror::Class> slot_class =
+              cl->LookupClass(caller_, descriptor.c_str(), loader);
+          DCHECK(!slot_class.IsNull());
+          return slot_class->IsAssignableFrom(set_class) ? OK : ERR(TYPE_MISMATCH);
+        }
+      }
+      case art::Primitive::kPrimByte:
+      case art::Primitive::kPrimChar:
+      case art::Primitive::kPrimInt:
+      case art::Primitive::kPrimShort:
+      case art::Primitive::kPrimBoolean:
+        return type_ == art::Primitive::kPrimInt ? OK : ERR(TYPE_MISMATCH);
+      case art::Primitive::kPrimLong:
+      case art::Primitive::kPrimFloat:
+      case art::Primitive::kPrimDouble:
+        return type_ == slot_type ? OK : ERR(TYPE_MISMATCH);
+      case art::Primitive::kPrimVoid:
+        LOG(FATAL) << "Unexpected primitive type " << slot_type;
+        UNREACHABLE();
+    }
+  }
+
+  jvmtiError Execute(art::ArtMethod* method, art::StackVisitor& visitor)
+      OVERRIDE REQUIRES(art::Locks::mutator_lock_) {
+    switch (type_) {
+      case art::Primitive::kPrimNot: {
+        uint32_t ptr_val;
+        art::ObjPtr<art::mirror::Object> obj(caller_->DecodeJObject(val_.l));
+        ptr_val = static_cast<uint32_t>(reinterpret_cast<uintptr_t>(obj.Ptr()));
+        if (!visitor.SetVReg(method,
+                             static_cast<uint16_t>(slot_),
+                             ptr_val,
+                             art::kReferenceVReg)) {
+          return ERR(OPAQUE_FRAME);
+        }
+        break;
+      }
+      case art::Primitive::kPrimInt:
+      case art::Primitive::kPrimFloat: {
+        if (!visitor.SetVReg(method,
+                             static_cast<uint16_t>(slot_),
+                             static_cast<uint32_t>(val_.i),
+                             type_ == art::Primitive::kPrimFloat ? art::kFloatVReg
+                                                                 : art::kIntVReg)) {
+          return ERR(OPAQUE_FRAME);
+        }
+        break;
+      }
+      case art::Primitive::kPrimDouble:
+      case art::Primitive::kPrimLong: {
+        auto lo_type = type_ == art::Primitive::kPrimLong ? art::kLongLoVReg : art::kDoubleLoVReg;
+        auto high_type = type_ == art::Primitive::kPrimLong ? art::kLongHiVReg : art::kDoubleHiVReg;
+        if (!visitor.SetVRegPair(method,
+                                 static_cast<uint16_t>(slot_),
+                                 static_cast<uint64_t>(val_.j),
+                                 lo_type,
+                                 high_type)) {
+          return ERR(OPAQUE_FRAME);
+        }
+        break;
+      }
+      default: {
+        LOG(FATAL) << "unexpected register type " << type_;
+        UNREACHABLE();
+      }
+    }
+    return OK;
+  }
+
+ private:
+  art::Primitive::Type type_;
+  jvalue val_;
+};
+
+jvmtiError MethodUtil::SetLocalVariableGeneric(jvmtiEnv* env ATTRIBUTE_UNUSED,
+                                               jthread thread,
+                                               jint depth,
+                                               jint slot,
+                                               art::Primitive::Type type,
+                                               jvalue val) {
+  if (depth < 0) {
+    return ERR(ILLEGAL_ARGUMENT);
+  }
+  art::Thread* self = art::Thread::Current();
+  art::ScopedObjectAccess soa(self);
+  art::MutexLock mu(self, *art::Locks::thread_list_lock_);
+  art::Thread* target = ThreadUtil::GetNativeThread(thread, soa);
+  if (target == nullptr && thread == nullptr) {
+    return ERR(INVALID_THREAD);
+  }
+  if (target == nullptr) {
+    return ERR(THREAD_NOT_ALIVE);
+  }
+  SetLocalVariableClosure c(self, depth, slot, type, val);
+  if (!target->RequestSynchronousCheckpoint(&c)) {
+    return ERR(THREAD_NOT_ALIVE);
+  } else {
+    return c.GetResult();
+  }
+}
+
+class GetLocalInstanceClosure : public art::Closure {
+ public:
+  GetLocalInstanceClosure(art::Thread* caller, jint depth, jobject* val)
+      : result_(ERR(INTERNAL)),
+        caller_(caller),
+        depth_(depth),
+        val_(val) {}
+
+  void Run(art::Thread* self) OVERRIDE REQUIRES(art::Locks::mutator_lock_) {
+    art::Locks::mutator_lock_->AssertSharedHeld(art::Thread::Current());
+    std::unique_ptr<art::Context> context(art::Context::Create());
+    FindFrameAtDepthVisitor visitor(self, context.get(), depth_);
+    visitor.WalkStack();
+    if (!visitor.FoundFrame()) {
+      // Must have been a bad depth.
+      result_ = ERR(NO_MORE_FRAMES);
+      return;
+    }
+    art::ArtMethod* method = visitor.GetMethod();
+    if (!visitor.IsShadowFrame() && !method->IsNative() && !method->IsProxyMethod()) {
+      // TODO We really should support get/set for non-shadow frames.
+      result_ = ERR(OPAQUE_FRAME);
+      return;
+    }
+    result_ = OK;
+    art::ObjPtr<art::mirror::Object> obj = visitor.GetThisObject();
+    *val_ = obj.IsNull() ? nullptr : caller_->GetJniEnv()->AddLocalReference<jobject>(obj);
+  }
+
+  jvmtiError GetResult() const {
+    return result_;
+  }
+
+ private:
+  jvmtiError result_;
+  art::Thread* caller_;
+  jint depth_;
+  jobject* val_;
+};
+
+jvmtiError MethodUtil::GetLocalInstance(jvmtiEnv* env ATTRIBUTE_UNUSED,
+                                        jthread thread,
+                                        jint depth,
+                                        jobject* data) {
+  if (depth < 0) {
+    return ERR(ILLEGAL_ARGUMENT);
+  }
+  art::Thread* self = art::Thread::Current();
+  art::ScopedObjectAccess soa(self);
+  art::MutexLock mu(self, *art::Locks::thread_list_lock_);
+  art::Thread* target = ThreadUtil::GetNativeThread(thread, soa);
+  if (target == nullptr && thread == nullptr) {
+    return ERR(INVALID_THREAD);
+  }
+  if (target == nullptr) {
+    return ERR(THREAD_NOT_ALIVE);
+  }
+  GetLocalInstanceClosure c(self, depth, data);
+  if (!target->RequestSynchronousCheckpoint(&c)) {
+    return ERR(THREAD_NOT_ALIVE);
+  } else {
+    return c.GetResult();
+  }
+}
+
+#define FOR_JVMTI_JVALUE_TYPES(fn) \
+    fn(jint, art::Primitive::kPrimInt, i) \
+    fn(jlong, art::Primitive::kPrimLong, j) \
+    fn(jfloat, art::Primitive::kPrimFloat, f) \
+    fn(jdouble, art::Primitive::kPrimDouble, d) \
+    fn(jobject, art::Primitive::kPrimNot, l)
+
+namespace impl {
+
+template<typename T> void WriteJvalue(T, jvalue*);
+template<typename T> void ReadJvalue(jvalue, T*);
+template<typename T> art::Primitive::Type GetJNIType();
+
+#define JNI_TYPE_CHAR(type, prim, id) \
+template<> art::Primitive::Type GetJNIType<type>() { \
+  return prim; \
+}
+
+FOR_JVMTI_JVALUE_TYPES(JNI_TYPE_CHAR);
+
+#undef JNI_TYPE_CHAR
+
+#define RW_JVALUE(type, prim, id) \
+    template<> void ReadJvalue<type>(jvalue in, type* out) { \
+      *out = in.id; \
+    } \
+    template<> void WriteJvalue<type>(type in, jvalue* out) { \
+      out->id = in; \
+    }
+
+FOR_JVMTI_JVALUE_TYPES(RW_JVALUE);
+
+#undef RW_JVALUE
+
+}  // namespace impl
+
+template<typename T>
+jvmtiError MethodUtil::SetLocalVariable(jvmtiEnv* env,
+                                        jthread thread,
+                                        jint depth,
+                                        jint slot,
+                                        T data) {
+  jvalue v = {.j = 0};
+  art::Primitive::Type type = impl::GetJNIType<T>();
+  impl::WriteJvalue(data, &v);
+  return SetLocalVariableGeneric(env, thread, depth, slot, type, v);
+}
+
+template<typename T>
+jvmtiError MethodUtil::GetLocalVariable(jvmtiEnv* env,
+                                        jthread thread,
+                                        jint depth,
+                                        jint slot,
+                                        T* data) {
+  if (data == nullptr) {
+    return ERR(NULL_POINTER);
+  }
+  jvalue v = {.j = 0};
+  art::Primitive::Type type = impl::GetJNIType<T>();
+  jvmtiError err = GetLocalVariableGeneric(env, thread, depth, slot, type, &v);
+  if (err != OK) {
+    return err;
+  } else {
+    impl::ReadJvalue(v, data);
+    return OK;
+  }
+}
+
+#define GET_SET_LV(type, prim, id) \
+    template jvmtiError MethodUtil::GetLocalVariable<type>(jvmtiEnv*, jthread, jint, jint, type*); \
+    template jvmtiError MethodUtil::SetLocalVariable<type>(jvmtiEnv*, jthread, jint, jint, type);
+
+FOR_JVMTI_JVALUE_TYPES(GET_SET_LV);
+
+#undef GET_SET_LV
+
+#undef FOR_JVMTI_JVALUE_TYPES
+
+}  // namespace openjdkjvmti
diff --git a/runtime/openjdkjvmti/ti_method.h b/openjdkjvmti/ti_method.h
similarity index 70%
rename from runtime/openjdkjvmti/ti_method.h
rename to openjdkjvmti/ti_method.h
index d95a81b..e3578a4 100644
--- a/runtime/openjdkjvmti/ti_method.h
+++ b/openjdkjvmti/ti_method.h
@@ -29,11 +29,12 @@
  * questions.
  */
 
-#ifndef ART_RUNTIME_OPENJDKJVMTI_TI_METHOD_H_
-#define ART_RUNTIME_OPENJDKJVMTI_TI_METHOD_H_
+#ifndef ART_OPENJDKJVMTI_TI_METHOD_H_
+#define ART_OPENJDKJVMTI_TI_METHOD_H_
 
 #include "jni.h"
 #include "jvmti.h"
+#include "primitive.h"
 
 namespace openjdkjvmti {
 
@@ -80,8 +81,34 @@
   static jvmtiError IsMethodNative(jvmtiEnv* env, jmethodID method, jboolean* is_native_ptr);
   static jvmtiError IsMethodObsolete(jvmtiEnv* env, jmethodID method, jboolean* is_obsolete_ptr);
   static jvmtiError IsMethodSynthetic(jvmtiEnv* env, jmethodID method, jboolean* is_synthetic_ptr);
+  static jvmtiError GetLocalVariableTable(jvmtiEnv* env,
+                                          jmethodID method,
+                                          jint* entry_count_ptr,
+                                          jvmtiLocalVariableEntry** table_ptr);
+
+  template<typename T>
+  static jvmtiError SetLocalVariable(jvmtiEnv* env, jthread thread, jint depth, jint slot, T data);
+
+  template<typename T>
+  static jvmtiError GetLocalVariable(jvmtiEnv* env, jthread thread, jint depth, jint slot, T* data);
+
+  static jvmtiError GetLocalInstance(jvmtiEnv* env, jthread thread, jint depth, jobject* data);
+
+ private:
+  static jvmtiError SetLocalVariableGeneric(jvmtiEnv* env,
+                                            jthread thread,
+                                            jint depth,
+                                            jint slot,
+                                            art::Primitive::Type type,
+                                            jvalue value);
+  static jvmtiError GetLocalVariableGeneric(jvmtiEnv* env,
+                                            jthread thread,
+                                            jint depth,
+                                            jint slot,
+                                            art::Primitive::Type type,
+                                            jvalue* value);
 };
 
 }  // namespace openjdkjvmti
 
-#endif  // ART_RUNTIME_OPENJDKJVMTI_TI_METHOD_H_
+#endif  // ART_OPENJDKJVMTI_TI_METHOD_H_
diff --git a/runtime/openjdkjvmti/ti_monitor.cc b/openjdkjvmti/ti_monitor.cc
similarity index 100%
rename from runtime/openjdkjvmti/ti_monitor.cc
rename to openjdkjvmti/ti_monitor.cc
diff --git a/runtime/openjdkjvmti/ti_monitor.h b/openjdkjvmti/ti_monitor.h
similarity index 93%
rename from runtime/openjdkjvmti/ti_monitor.h
rename to openjdkjvmti/ti_monitor.h
index 96ccb0d..add089c 100644
--- a/runtime/openjdkjvmti/ti_monitor.h
+++ b/openjdkjvmti/ti_monitor.h
@@ -29,8 +29,8 @@
  * questions.
  */
 
-#ifndef ART_RUNTIME_OPENJDKJVMTI_TI_MONITOR_H_
-#define ART_RUNTIME_OPENJDKJVMTI_TI_MONITOR_H_
+#ifndef ART_OPENJDKJVMTI_TI_MONITOR_H_
+#define ART_OPENJDKJVMTI_TI_MONITOR_H_
 
 #include "jni.h"
 #include "jvmti.h"
@@ -56,4 +56,4 @@
 
 }  // namespace openjdkjvmti
 
-#endif  // ART_RUNTIME_OPENJDKJVMTI_TI_MONITOR_H_
+#endif  // ART_OPENJDKJVMTI_TI_MONITOR_H_
diff --git a/runtime/openjdkjvmti/ti_object.cc b/openjdkjvmti/ti_object.cc
similarity index 100%
rename from runtime/openjdkjvmti/ti_object.cc
rename to openjdkjvmti/ti_object.cc
diff --git a/runtime/openjdkjvmti/ti_object.h b/openjdkjvmti/ti_object.h
similarity index 92%
rename from runtime/openjdkjvmti/ti_object.h
rename to openjdkjvmti/ti_object.h
index 09eee61..fa3bd0f 100644
--- a/runtime/openjdkjvmti/ti_object.h
+++ b/openjdkjvmti/ti_object.h
@@ -29,8 +29,8 @@
  * questions.
  */
 
-#ifndef ART_RUNTIME_OPENJDKJVMTI_TI_OBJECT_H_
-#define ART_RUNTIME_OPENJDKJVMTI_TI_OBJECT_H_
+#ifndef ART_OPENJDKJVMTI_TI_OBJECT_H_
+#define ART_OPENJDKJVMTI_TI_OBJECT_H_
 
 #include "jni.h"
 #include "jvmti.h"
@@ -46,4 +46,4 @@
 
 }  // namespace openjdkjvmti
 
-#endif  // ART_RUNTIME_OPENJDKJVMTI_TI_OBJECT_H_
+#endif  // ART_OPENJDKJVMTI_TI_OBJECT_H_
diff --git a/runtime/openjdkjvmti/ti_phase.cc b/openjdkjvmti/ti_phase.cc
similarity index 100%
rename from runtime/openjdkjvmti/ti_phase.cc
rename to openjdkjvmti/ti_phase.cc
diff --git a/runtime/openjdkjvmti/ti_phase.h b/openjdkjvmti/ti_phase.h
similarity index 93%
rename from runtime/openjdkjvmti/ti_phase.h
rename to openjdkjvmti/ti_phase.h
index a2c0d11..d4ed86b 100644
--- a/runtime/openjdkjvmti/ti_phase.h
+++ b/openjdkjvmti/ti_phase.h
@@ -29,8 +29,8 @@
  * questions.
  */
 
-#ifndef ART_RUNTIME_OPENJDKJVMTI_TI_PHASE_H_
-#define ART_RUNTIME_OPENJDKJVMTI_TI_PHASE_H_
+#ifndef ART_OPENJDKJVMTI_TI_PHASE_H_
+#define ART_OPENJDKJVMTI_TI_PHASE_H_
 
 #include "jni.h"
 #include "jvmti.h"
@@ -66,4 +66,4 @@
 
 }  // namespace openjdkjvmti
 
-#endif  // ART_RUNTIME_OPENJDKJVMTI_TI_PHASE_H_
+#endif  // ART_OPENJDKJVMTI_TI_PHASE_H_
diff --git a/runtime/openjdkjvmti/ti_properties.cc b/openjdkjvmti/ti_properties.cc
similarity index 100%
rename from runtime/openjdkjvmti/ti_properties.cc
rename to openjdkjvmti/ti_properties.cc
diff --git a/runtime/openjdkjvmti/ti_properties.h b/openjdkjvmti/ti_properties.h
similarity index 92%
rename from runtime/openjdkjvmti/ti_properties.h
rename to openjdkjvmti/ti_properties.h
index 7073481..187b85d 100644
--- a/runtime/openjdkjvmti/ti_properties.h
+++ b/openjdkjvmti/ti_properties.h
@@ -29,8 +29,8 @@
  * questions.
  */
 
-#ifndef ART_RUNTIME_OPENJDKJVMTI_TI_PROPERTIES_H_
-#define ART_RUNTIME_OPENJDKJVMTI_TI_PROPERTIES_H_
+#ifndef ART_OPENJDKJVMTI_TI_PROPERTIES_H_
+#define ART_OPENJDKJVMTI_TI_PROPERTIES_H_
 
 #include "jni.h"
 #include "jvmti.h"
@@ -48,4 +48,4 @@
 
 }  // namespace openjdkjvmti
 
-#endif  // ART_RUNTIME_OPENJDKJVMTI_TI_PROPERTIES_H_
+#endif  // ART_OPENJDKJVMTI_TI_PROPERTIES_H_
diff --git a/runtime/openjdkjvmti/ti_redefine.cc b/openjdkjvmti/ti_redefine.cc
similarity index 100%
rename from runtime/openjdkjvmti/ti_redefine.cc
rename to openjdkjvmti/ti_redefine.cc
diff --git a/runtime/openjdkjvmti/ti_redefine.h b/openjdkjvmti/ti_redefine.h
similarity index 98%
rename from runtime/openjdkjvmti/ti_redefine.h
rename to openjdkjvmti/ti_redefine.h
index 03b4bf2..984f922 100644
--- a/runtime/openjdkjvmti/ti_redefine.h
+++ b/openjdkjvmti/ti_redefine.h
@@ -29,8 +29,8 @@
  * questions.
  */
 
-#ifndef ART_RUNTIME_OPENJDKJVMTI_TI_REDEFINE_H_
-#define ART_RUNTIME_OPENJDKJVMTI_TI_REDEFINE_H_
+#ifndef ART_OPENJDKJVMTI_TI_REDEFINE_H_
+#define ART_OPENJDKJVMTI_TI_REDEFINE_H_
 
 #include <string>
 
@@ -265,4 +265,4 @@
 
 }  // namespace openjdkjvmti
 
-#endif  // ART_RUNTIME_OPENJDKJVMTI_TI_REDEFINE_H_
+#endif  // ART_OPENJDKJVMTI_TI_REDEFINE_H_
diff --git a/runtime/openjdkjvmti/ti_search.cc b/openjdkjvmti/ti_search.cc
similarity index 100%
rename from runtime/openjdkjvmti/ti_search.cc
rename to openjdkjvmti/ti_search.cc
diff --git a/runtime/openjdkjvmti/ti_search.h b/openjdkjvmti/ti_search.h
similarity index 92%
rename from runtime/openjdkjvmti/ti_search.h
rename to openjdkjvmti/ti_search.h
index cd7b4be..81a28cc 100644
--- a/runtime/openjdkjvmti/ti_search.h
+++ b/openjdkjvmti/ti_search.h
@@ -29,8 +29,8 @@
  * questions.
  */
 
-#ifndef ART_RUNTIME_OPENJDKJVMTI_TI_SEARCH_H_
-#define ART_RUNTIME_OPENJDKJVMTI_TI_SEARCH_H_
+#ifndef ART_OPENJDKJVMTI_TI_SEARCH_H_
+#define ART_OPENJDKJVMTI_TI_SEARCH_H_
 
 #include <vector>
 
@@ -50,4 +50,4 @@
 
 }  // namespace openjdkjvmti
 
-#endif  // ART_RUNTIME_OPENJDKJVMTI_TI_SEARCH_H_
+#endif  // ART_OPENJDKJVMTI_TI_SEARCH_H_
diff --git a/runtime/openjdkjvmti/ti_stack.cc b/openjdkjvmti/ti_stack.cc
similarity index 100%
rename from runtime/openjdkjvmti/ti_stack.cc
rename to openjdkjvmti/ti_stack.cc
diff --git a/runtime/openjdkjvmti/ti_stack.h b/openjdkjvmti/ti_stack.h
similarity index 95%
rename from runtime/openjdkjvmti/ti_stack.h
rename to openjdkjvmti/ti_stack.h
index 6a593cf..2e96b82 100644
--- a/runtime/openjdkjvmti/ti_stack.h
+++ b/openjdkjvmti/ti_stack.h
@@ -29,8 +29,8 @@
  * questions.
  */
 
-#ifndef ART_RUNTIME_OPENJDKJVMTI_TI_STACK_H_
-#define ART_RUNTIME_OPENJDKJVMTI_TI_STACK_H_
+#ifndef ART_OPENJDKJVMTI_TI_STACK_H_
+#define ART_OPENJDKJVMTI_TI_STACK_H_
 
 #include "jni.h"
 #include "jvmti.h"
@@ -71,4 +71,4 @@
 
 }  // namespace openjdkjvmti
 
-#endif  // ART_RUNTIME_OPENJDKJVMTI_TI_STACK_H_
+#endif  // ART_OPENJDKJVMTI_TI_STACK_H_
diff --git a/runtime/openjdkjvmti/ti_thread.cc b/openjdkjvmti/ti_thread.cc
similarity index 81%
rename from runtime/openjdkjvmti/ti_thread.cc
rename to openjdkjvmti/ti_thread.cc
index 9acea2a..6fa73f8 100644
--- a/runtime/openjdkjvmti/ti_thread.cc
+++ b/openjdkjvmti/ti_thread.cc
@@ -159,26 +159,13 @@
   return ERR(NONE);
 }
 
-static art::Thread* GetNativeThreadLocked(jthread thread,
-                                          const art::ScopedObjectAccessAlreadyRunnable& soa)
-    REQUIRES_SHARED(art::Locks::mutator_lock_)
-    REQUIRES(art::Locks::thread_list_lock_) {
-  if (thread == nullptr) {
-    return art::Thread::Current();
-  }
-
-  return art::Thread::FromManagedThread(soa, thread);
-}
-
 // Get the native thread. The spec says a null object denotes the current thread.
-static art::Thread* GetNativeThread(jthread thread,
-                                    const art::ScopedObjectAccessAlreadyRunnable& soa)
-    REQUIRES_SHARED(art::Locks::mutator_lock_) {
+art::Thread* ThreadUtil::GetNativeThread(jthread thread,
+                                         const art::ScopedObjectAccessAlreadyRunnable& soa) {
   if (thread == nullptr) {
     return art::Thread::Current();
   }
 
-  art::MutexLock mu(soa.Self(), *art::Locks::thread_list_lock_);
   return art::Thread::FromManagedThread(soa, thread);
 }
 
@@ -190,18 +177,20 @@
     return JVMTI_ERROR_WRONG_PHASE;
   }
 
-  art::ScopedObjectAccess soa(art::Thread::Current());
+  art::Thread* self = art::Thread::Current();
+  art::ScopedObjectAccess soa(self);
+  art::MutexLock mu(self, *art::Locks::thread_list_lock_);
 
-  art::Thread* self = GetNativeThread(thread, soa);
-  if (self == nullptr && thread == nullptr) {
+  art::Thread* target = GetNativeThread(thread, soa);
+  if (target == nullptr && thread == nullptr) {
     return ERR(INVALID_THREAD);
   }
 
   JvmtiUniquePtr<char[]> name_uptr;
-  if (self != nullptr) {
+  if (target != nullptr) {
     // Have a native thread object, this thread is alive.
     std::string name;
-    self->GetThreadName(name);
+    target->GetThreadName(name);
     jvmtiError name_result;
     name_uptr = CopyString(env, name.c_str(), &name_result);
     if (name_uptr == nullptr) {
@@ -209,11 +198,11 @@
     }
     info_ptr->name = name_uptr.get();
 
-    info_ptr->priority = self->GetNativePriority();
+    info_ptr->priority = target->GetNativePriority();
 
-    info_ptr->is_daemon = self->IsDaemon();
+    info_ptr->is_daemon = target->IsDaemon();
 
-    art::ObjPtr<art::mirror::Object> peer = self->GetPeerFromOtherThread();
+    art::ObjPtr<art::mirror::Object> peer = target->GetPeerFromOtherThread();
 
     // ThreadGroup.
     if (peer != nullptr) {
@@ -310,9 +299,8 @@
 static InternalThreadState GetNativeThreadState(jthread thread,
                                                 const art::ScopedObjectAccessAlreadyRunnable& soa)
     REQUIRES_SHARED(art::Locks::mutator_lock_)
-    REQUIRES(art::Locks::user_code_suspension_lock_) {
+    REQUIRES(art::Locks::thread_list_lock_, art::Locks::user_code_suspension_lock_) {
   art::Thread* self = nullptr;
-  art::MutexLock tll_mu(soa.Self(), *art::Locks::thread_list_lock_);
   if (thread == nullptr) {
     self = art::Thread::Current();
   } else {
@@ -456,43 +444,46 @@
       }
     }
     art::ScopedObjectAccess soa(self);
+    art::MutexLock tll_mu(self, *art::Locks::thread_list_lock_);
     state = GetNativeThreadState(thread, soa);
-    break;
+    if (state.art_state == art::ThreadState::kStarting) {
+      break;
+    }
+    DCHECK(state.native_thread != nullptr);
+
+    // Translate internal thread state to JVMTI and Java state.
+    jint jvmti_state = GetJvmtiThreadStateFromInternal(state);
+
+    // Java state is derived from nativeGetState.
+    // TODO: Our implementation assigns "runnable" to suspended. As such, we will have slightly
+    //       different mask if a thread got suspended due to user-code. However, this is for
+    //       consistency with the Java view.
+    jint java_state = GetJavaStateFromInternal(state);
+
+    *thread_state_ptr = jvmti_state | java_state;
+
+    return ERR(NONE);
   } while (true);
 
-  if (state.art_state == art::ThreadState::kStarting) {
-    if (thread == nullptr) {
-      // No native thread, and no Java thread? We must be starting up. Report as wrong phase.
-      return ERR(WRONG_PHASE);
-    }
+  DCHECK_EQ(state.art_state, art::ThreadState::kStarting);
 
-    art::ScopedObjectAccess soa(self);
-
-    // Need to read the Java "started" field to know whether this is starting or terminated.
-    art::ObjPtr<art::mirror::Object> peer = soa.Decode<art::mirror::Object>(thread);
-    art::ObjPtr<art::mirror::Class> klass = peer->GetClass();
-    art::ArtField* started_field = klass->FindDeclaredInstanceField("started", "Z");
-    CHECK(started_field != nullptr);
-    bool started = started_field->GetBoolean(peer) != 0;
-    constexpr jint kStartedState = JVMTI_JAVA_LANG_THREAD_STATE_NEW;
-    constexpr jint kTerminatedState = JVMTI_THREAD_STATE_TERMINATED |
-                                      JVMTI_JAVA_LANG_THREAD_STATE_TERMINATED;
-    *thread_state_ptr = started ? kTerminatedState : kStartedState;
-    return ERR(NONE);
+  if (thread == nullptr) {
+    // No native thread, and no Java thread? We must be starting up. Report as wrong phase.
+    return ERR(WRONG_PHASE);
   }
-  DCHECK(state.native_thread != nullptr);
 
-  // Translate internal thread state to JVMTI and Java state.
-  jint jvmti_state = GetJvmtiThreadStateFromInternal(state);
+  art::ScopedObjectAccess soa(self);
 
-  // Java state is derived from nativeGetState.
-  // TODO: Our implementation assigns "runnable" to suspended. As such, we will have slightly
-  //       different mask if a thread got suspended due to user-code. However, this is for
-  //       consistency with the Java view.
-  jint java_state = GetJavaStateFromInternal(state);
-
-  *thread_state_ptr = jvmti_state | java_state;
-
+  // Need to read the Java "started" field to know whether this is starting or terminated.
+  art::ObjPtr<art::mirror::Object> peer = soa.Decode<art::mirror::Object>(thread);
+  art::ObjPtr<art::mirror::Class> klass = peer->GetClass();
+  art::ArtField* started_field = klass->FindDeclaredInstanceField("started", "Z");
+  CHECK(started_field != nullptr);
+  bool started = started_field->GetBoolean(peer) != 0;
+  constexpr jint kStartedState = JVMTI_JAVA_LANG_THREAD_STATE_NEW;
+  constexpr jint kTerminatedState = JVMTI_THREAD_STATE_TERMINATED |
+                                    JVMTI_JAVA_LANG_THREAD_STATE_TERMINATED;
+  *thread_state_ptr = started ? kTerminatedState : kStartedState;
   return ERR(NONE);
 }
 
@@ -571,7 +562,7 @@
   art::Thread* self = art::Thread::Current();
   art::ScopedObjectAccess soa(self);
   art::MutexLock mu(self, *art::Locks::thread_list_lock_);
-  art::Thread* target = GetNativeThreadLocked(thread, soa);
+  art::Thread* target = GetNativeThread(thread, soa);
   if (target == nullptr && thread == nullptr) {
     return ERR(INVALID_THREAD);
   }
@@ -600,7 +591,7 @@
   art::Thread* self = art::Thread::Current();
   art::ScopedObjectAccess soa(self);
   art::MutexLock mu(self, *art::Locks::thread_list_lock_);
-  art::Thread* target = GetNativeThreadLocked(thread, soa);
+  art::Thread* target = GetNativeThread(thread, soa);
   if (target == nullptr && thread == nullptr) {
     return ERR(INVALID_THREAD);
   }
@@ -700,8 +691,7 @@
 }
 
 jvmtiError ThreadUtil::SuspendOther(art::Thread* self,
-                                    jthread target_jthread,
-                                    const art::Thread* target) {
+                                    jthread target_jthread) {
   // Loop since we need to bail out and try again if we would end up getting suspended while holding
   // the user_code_suspension_lock_ due to a SuspendReason::kForUserCode. In this situation we
   // release the lock, wait to get resumed and try again.
@@ -714,33 +704,43 @@
     SuspendCheck(self);
     art::MutexLock mu(self, *art::Locks::user_code_suspension_lock_);
     {
-      art::MutexLock thread_list_mu(self, *art::Locks::thread_suspend_count_lock_);
+      art::MutexLock thread_suspend_count_mu(self, *art::Locks::thread_suspend_count_lock_);
       // Make sure we won't be suspended in the middle of holding the thread_suspend_count_lock_ by
       // a user-code suspension. We retry and do another SuspendCheck to clear this.
       if (self->GetUserCodeSuspendCount() != 0) {
         continue;
-      } else if (target->GetUserCodeSuspendCount() != 0) {
-        return ERR(THREAD_SUSPENDED);
       }
+      // We are not going to be suspended by user code from now on.
     }
-    bool timeout = true;
-    while (timeout) {
+    {
+      art::ScopedObjectAccess soa(self);
+      art::MutexLock thread_list_mu(self, *art::Locks::thread_list_lock_);
+      art::Thread* target = GetNativeThread(target_jthread, soa);
       art::ThreadState state = target->GetState();
       if (state == art::ThreadState::kTerminated || state == art::ThreadState::kStarting) {
         return ERR(THREAD_NOT_ALIVE);
-      }
-      art::Thread* ret_target = art::Runtime::Current()->GetThreadList()->SuspendThreadByPeer(
-          target_jthread,
-          /* request_suspension */ true,
-          art::SuspendReason::kForUserCode,
-          &timeout);
-      if (ret_target == nullptr && !timeout) {
-        // TODO It would be good to get more information about why exactly the thread failed to
-        // suspend.
-        return ERR(INTERNAL);
+      } else {
+        art::MutexLock thread_suspend_count_mu(self, *art::Locks::thread_suspend_count_lock_);
+        if (target->GetUserCodeSuspendCount() != 0) {
+          return ERR(THREAD_SUSPENDED);
+        }
       }
     }
-    return OK;
+    bool timeout = true;
+    art::Thread* ret_target = art::Runtime::Current()->GetThreadList()->SuspendThreadByPeer(
+        target_jthread,
+        /* request_suspension */ true,
+        art::SuspendReason::kForUserCode,
+        &timeout);
+    if (ret_target == nullptr && !timeout) {
+      // TODO It would be good to get more information about why exactly the thread failed to
+      // suspend.
+      return ERR(INTERNAL);
+    } else if (!timeout) {
+      // we didn't time out and got a result.
+      return OK;
+    }
+    // We timed out. Just go around and try again.
   } while (true);
   UNREACHABLE();
 }
@@ -769,18 +769,21 @@
 
 jvmtiError ThreadUtil::SuspendThread(jvmtiEnv* env ATTRIBUTE_UNUSED, jthread thread) {
   art::Thread* self = art::Thread::Current();
-  art::Thread* target;
+  bool target_is_self = false;
   {
     art::ScopedObjectAccess soa(self);
-    target = GetNativeThread(thread, soa);
+    art::MutexLock mu(self, *art::Locks::thread_list_lock_);
+    art::Thread* target = GetNativeThread(thread, soa);
+    if (target == nullptr) {
+      return ERR(INVALID_THREAD);
+    } else if (target == self) {
+      target_is_self = true;
+    }
   }
-  if (target == nullptr) {
-    return ERR(INVALID_THREAD);
-  }
-  if (target == self) {
+  if (target_is_self) {
     return SuspendSelf(self);
   } else {
-    return SuspendOther(self, thread, target);
+    return SuspendOther(self, thread);
   }
 }
 
@@ -791,41 +794,56 @@
   }
   art::Thread* self = art::Thread::Current();
   art::Thread* target;
-  {
-    // NB This does a SuspendCheck (during thread state change) so we need to make sure we don't
-    // have the 'suspend_lock' locked here.
-    art::ScopedObjectAccess soa(self);
-    target = GetNativeThread(thread, soa);
-  }
-  if (target == nullptr) {
-    return ERR(INVALID_THREAD);
-  } else if (target == self) {
-    // We would have paused until we aren't suspended anymore due to the ScopedObjectAccess so we
-    // can just return THREAD_NOT_SUSPENDED. Unfortunately we cannot do any real DCHECKs about
-    // current state since it's all concurrent.
-    return ERR(THREAD_NOT_SUSPENDED);
-  }
-  // Now that we know we aren't getting suspended ourself (since we have a mutator lock) we lock the
-  // suspend_lock to start suspending.
-  art::MutexLock mu(self, *art::Locks::user_code_suspension_lock_);
-  {
-    // The JVMTI spec requires us to return THREAD_NOT_SUSPENDED if it is alive but we really cannot
-    // tell why resume failed.
-    art::MutexLock thread_list_mu(self, *art::Locks::thread_suspend_count_lock_);
-    if (target->GetUserCodeSuspendCount() == 0) {
-      return ERR(THREAD_NOT_SUSPENDED);
+  // Retry until we know we won't get suspended by user code while resuming something.
+  do {
+    SuspendCheck(self);
+    art::MutexLock ucsl_mu(self, *art::Locks::user_code_suspension_lock_);
+    {
+      art::MutexLock tscl_mu(self, *art::Locks::thread_suspend_count_lock_);
+      // Make sure we won't be suspended in the middle of holding the thread_suspend_count_lock_ by
+      // a user-code suspension. We retry and do another SuspendCheck to clear this.
+      if (self->GetUserCodeSuspendCount() != 0) {
+        continue;
+      }
     }
-  }
-  if (target->GetState() == art::ThreadState::kTerminated) {
-    return ERR(THREAD_NOT_ALIVE);
-  }
-  DCHECK(target != self);
-  if (!art::Runtime::Current()->GetThreadList()->Resume(target, art::SuspendReason::kForUserCode)) {
-    // TODO Give a better error.
-    // This is most likely THREAD_NOT_SUSPENDED but we cannot really be sure.
-    return ERR(INTERNAL);
-  }
-  return OK;
+    // From now on we know we cannot get suspended by user-code.
+    {
+      // NB This does a SuspendCheck (during thread state change) so we need to make sure we don't
+      // have the 'suspend_lock' locked here.
+      art::ScopedObjectAccess soa(self);
+      art::MutexLock tll_mu(self, *art::Locks::thread_list_lock_);
+      target = GetNativeThread(thread, soa);
+      if (target == nullptr) {
+        return ERR(INVALID_THREAD);
+      } else if (target == self) {
+        // We would have paused until we aren't suspended anymore due to the ScopedObjectAccess so
+        // we can just return THREAD_NOT_SUSPENDED. Unfortunately we cannot do any real DCHECKs
+        // about current state since it's all concurrent.
+        return ERR(THREAD_NOT_SUSPENDED);
+      } else if (target->GetState() == art::ThreadState::kTerminated) {
+        return ERR(THREAD_NOT_ALIVE);
+      }
+      // The JVMTI spec requires us to return THREAD_NOT_SUSPENDED if it is alive but we really
+      // cannot tell why resume failed.
+      {
+        art::MutexLock thread_suspend_count_mu(self, *art::Locks::thread_suspend_count_lock_);
+        if (target->GetUserCodeSuspendCount() == 0) {
+          return ERR(THREAD_NOT_SUSPENDED);
+        }
+      }
+    }
+    // It is okay that we don't have a thread_list_lock here since we know that the thread cannot
+    // die since it is currently held suspended by a SuspendReason::kForUserCode suspend.
+    DCHECK(target != self);
+    if (!art::Runtime::Current()->GetThreadList()->Resume(target,
+                                                          art::SuspendReason::kForUserCode)) {
+      // TODO Give a better error.
+      // This is most likely THREAD_NOT_SUSPENDED but we cannot really be sure.
+      return ERR(INTERNAL);
+    } else {
+      return OK;
+    }
+  } while (true);
 }
 
 // Suspends all the threads in the list at the same time. Getting this behavior is a little tricky
@@ -851,6 +869,7 @@
   for (jint i = 0; i < request_count; i++) {
     {
       art::ScopedObjectAccess soa(self);
+      art::MutexLock mu(self, *art::Locks::thread_list_lock_);
       if (threads[i] == nullptr || GetNativeThread(threads[i], soa) == self) {
         current_thread_indexes.push_back(i);
         continue;
diff --git a/runtime/openjdkjvmti/ti_thread.h b/openjdkjvmti/ti_thread.h
similarity index 89%
rename from runtime/openjdkjvmti/ti_thread.h
rename to openjdkjvmti/ti_thread.h
index 0f7e837..03c49d7 100644
--- a/runtime/openjdkjvmti/ti_thread.h
+++ b/openjdkjvmti/ti_thread.h
@@ -29,16 +29,18 @@
  * questions.
  */
 
-#ifndef ART_RUNTIME_OPENJDKJVMTI_TI_THREAD_H_
-#define ART_RUNTIME_OPENJDKJVMTI_TI_THREAD_H_
+#ifndef ART_OPENJDKJVMTI_TI_THREAD_H_
+#define ART_OPENJDKJVMTI_TI_THREAD_H_
 
 #include "jni.h"
 #include "jvmti.h"
 
+#include "base/macros.h"
 #include "base/mutex.h"
 
 namespace art {
 class ArtField;
+class ScopedObjectAccessAlreadyRunnable;
 class Thread;
 }  // namespace art
 
@@ -86,6 +88,11 @@
                                      const jthread* threads,
                                      jvmtiError* results);
 
+  static art::Thread* GetNativeThread(jthread thread,
+                                      const art::ScopedObjectAccessAlreadyRunnable& soa)
+      REQUIRES_SHARED(art::Locks::mutator_lock_)
+      REQUIRES(art::Locks::thread_list_lock_);
+
  private:
   // We need to make sure only one thread tries to suspend threads at a time so we can get the
   // 'suspend-only-once' behavior the spec requires. Internally, ART considers suspension to be a
@@ -98,9 +105,7 @@
   // cause the thread to wake up if the thread is suspended for the debugger or gc or something.
   static jvmtiError SuspendSelf(art::Thread* self)
       REQUIRES(!art::Locks::mutator_lock_, !art::Locks::user_code_suspension_lock_);
-  static jvmtiError SuspendOther(art::Thread* self,
-                                 jthread target_jthread,
-                                 const art::Thread* target)
+  static jvmtiError SuspendOther(art::Thread* self, jthread target_jthread)
       REQUIRES(!art::Locks::mutator_lock_, !art::Locks::user_code_suspension_lock_);
 
   static art::ArtField* context_class_loader_;
@@ -108,4 +113,4 @@
 
 }  // namespace openjdkjvmti
 
-#endif  // ART_RUNTIME_OPENJDKJVMTI_TI_THREAD_H_
+#endif  // ART_OPENJDKJVMTI_TI_THREAD_H_
diff --git a/runtime/openjdkjvmti/ti_threadgroup.cc b/openjdkjvmti/ti_threadgroup.cc
similarity index 100%
rename from runtime/openjdkjvmti/ti_threadgroup.cc
rename to openjdkjvmti/ti_threadgroup.cc
diff --git a/runtime/openjdkjvmti/ti_threadgroup.h b/openjdkjvmti/ti_threadgroup.h
similarity index 93%
rename from runtime/openjdkjvmti/ti_threadgroup.h
rename to openjdkjvmti/ti_threadgroup.h
index c3a0ff5..4911566 100644
--- a/runtime/openjdkjvmti/ti_threadgroup.h
+++ b/openjdkjvmti/ti_threadgroup.h
@@ -29,8 +29,8 @@
  * questions.
  */
 
-#ifndef ART_RUNTIME_OPENJDKJVMTI_TI_THREADGROUP_H_
-#define ART_RUNTIME_OPENJDKJVMTI_TI_THREADGROUP_H_
+#ifndef ART_OPENJDKJVMTI_TI_THREADGROUP_H_
+#define ART_OPENJDKJVMTI_TI_THREADGROUP_H_
 
 #include "jni.h"
 #include "jvmti.h"
@@ -57,4 +57,4 @@
 
 }  // namespace openjdkjvmti
 
-#endif  // ART_RUNTIME_OPENJDKJVMTI_TI_THREADGROUP_H_
+#endif  // ART_OPENJDKJVMTI_TI_THREADGROUP_H_
diff --git a/runtime/openjdkjvmti/ti_timers.cc b/openjdkjvmti/ti_timers.cc
similarity index 100%
rename from runtime/openjdkjvmti/ti_timers.cc
rename to openjdkjvmti/ti_timers.cc
diff --git a/runtime/openjdkjvmti/ti_timers.h b/openjdkjvmti/ti_timers.h
similarity index 92%
rename from runtime/openjdkjvmti/ti_timers.h
rename to openjdkjvmti/ti_timers.h
index 6300678..892205a 100644
--- a/runtime/openjdkjvmti/ti_timers.h
+++ b/openjdkjvmti/ti_timers.h
@@ -29,8 +29,8 @@
  * questions.
  */
 
-#ifndef ART_RUNTIME_OPENJDKJVMTI_TI_TIMERS_H_
-#define ART_RUNTIME_OPENJDKJVMTI_TI_TIMERS_H_
+#ifndef ART_OPENJDKJVMTI_TI_TIMERS_H_
+#define ART_OPENJDKJVMTI_TI_TIMERS_H_
 
 #include "jni.h"
 #include "jvmti.h"
@@ -48,4 +48,4 @@
 
 }  // namespace openjdkjvmti
 
-#endif  // ART_RUNTIME_OPENJDKJVMTI_TI_TIMERS_H_
+#endif  // ART_OPENJDKJVMTI_TI_TIMERS_H_
diff --git a/runtime/openjdkjvmti/transform.cc b/openjdkjvmti/transform.cc
similarity index 100%
rename from runtime/openjdkjvmti/transform.cc
rename to openjdkjvmti/transform.cc
diff --git a/runtime/openjdkjvmti/transform.h b/openjdkjvmti/transform.h
similarity index 94%
rename from runtime/openjdkjvmti/transform.h
rename to openjdkjvmti/transform.h
index ed24068..6bbe60a 100644
--- a/runtime/openjdkjvmti/transform.h
+++ b/openjdkjvmti/transform.h
@@ -29,8 +29,8 @@
  * questions.
  */
 
-#ifndef ART_RUNTIME_OPENJDKJVMTI_TRANSFORM_H_
-#define ART_RUNTIME_OPENJDKJVMTI_TRANSFORM_H_
+#ifndef ART_OPENJDKJVMTI_TRANSFORM_H_
+#define ART_OPENJDKJVMTI_TRANSFORM_H_
 
 #include <string>
 
@@ -65,5 +65,5 @@
 
 }  // namespace openjdkjvmti
 
-#endif  // ART_RUNTIME_OPENJDKJVMTI_TRANSFORM_H_
+#endif  // ART_OPENJDKJVMTI_TRANSFORM_H_
 
diff --git a/runtime/Android.bp b/runtime/Android.bp
index 8d15c34..d534542 100644
--- a/runtime/Android.bp
+++ b/runtime/Android.bp
@@ -25,6 +25,7 @@
     defaults: ["art_defaults"],
     host_supported: true,
     srcs: [
+        "aot_class_linker.cc",
         "art_field.cc",
         "art_method.cc",
         "atomic.cc",
@@ -50,7 +51,6 @@
         "class_linker.cc",
         "class_loader_context.cc",
         "class_table.cc",
-        "code_simulator_container.cc",
         "common_throws.cc",
         "compiler_filter.cc",
         "debugger.cc",
@@ -625,9 +625,3 @@
         "libvixld-arm64",
     ],
 }
-
-subdirs = [
-    "openjdkjvm",
-    "openjdkjvmti",
-    "simulator",
-]
diff --git a/runtime/aot_class_linker.cc b/runtime/aot_class_linker.cc
new file mode 100644
index 0000000..b1bc3f8
--- /dev/null
+++ b/runtime/aot_class_linker.cc
@@ -0,0 +1,69 @@
+/*
+ * 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 "aot_class_linker.h"
+
+#include "handle_scope-inl.h"
+#include "mirror/class.h"
+#include "mirror/object-inl.h"
+#include "runtime.h"
+
+namespace art {
+
+AotClassLinker::AotClassLinker(InternTable *intern_table) : ClassLinker(intern_table) {}
+
+AotClassLinker::~AotClassLinker() {}
+
+// Wrap the original InitializeClass with creation of transaction when in strict mode.
+bool AotClassLinker::InitializeClass(Thread* self, Handle<mirror::Class> klass,
+                                  bool can_init_statics, bool can_init_parents) {
+  Runtime* const runtime = Runtime::Current();
+  bool strict_mode_ = runtime->IsActiveStrictTransactionMode();
+
+  DCHECK(klass != nullptr);
+  if (klass->IsInitialized() || klass->IsInitializing()) {
+    return ClassLinker::InitializeClass(self, klass, can_init_statics, can_init_parents);
+  }
+
+  // Don't initialize klass if it's superclass is not initialized, because superclass might abort
+  // the transaction and rolled back after klass's change is commited.
+  if (strict_mode_ && !klass->IsInterface() && klass->HasSuperClass()) {
+    if (klass->GetSuperClass()->GetStatus() == mirror::Class::kStatusInitializing) {
+      runtime->AbortTransactionAndThrowAbortError(self, "Can't resolve "
+          + klass->PrettyTypeOf() + " because it's superclass is not initialized.");
+      return false;
+    }
+  }
+
+  if (strict_mode_) {
+    runtime->EnterTransactionMode(true, klass.Get()->AsClass());
+  }
+  bool success = ClassLinker::InitializeClass(self, klass, can_init_statics, can_init_parents);
+
+  if (strict_mode_) {
+    if (success) {
+      // Exit Transaction if success.
+      runtime->ExitTransactionMode();
+    } else {
+      // If not successfully initialized, the last transaction must abort. Don't rollback
+      // immediately, leave the cleanup to compiler driver which needs abort message and exception.
+      DCHECK(runtime->IsTransactionAborted());
+      DCHECK(self->IsExceptionPending());
+    }
+  }
+  return success;
+}
+}  // namespace art
diff --git a/runtime/aot_class_linker.h b/runtime/aot_class_linker.h
new file mode 100644
index 0000000..11bea86
--- /dev/null
+++ b/runtime/aot_class_linker.h
@@ -0,0 +1,40 @@
+/*
+ * 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_AOT_CLASS_LINKER_H_
+#define ART_RUNTIME_AOT_CLASS_LINKER_H_
+
+#include "class_linker.h"
+
+namespace art {
+// AotClassLinker is only used for AOT compiler, which includes some logic for class initialization
+// which will only be used in pre-compilation.
+class AotClassLinker : public ClassLinker {
+ public:
+  explicit AotClassLinker(InternTable *intern_table);
+  ~AotClassLinker();
+
+  bool InitializeClass(Thread *self,
+                       Handle<mirror::Class> klass,
+                       bool can_run_clinit,
+                       bool can_init_parents)
+      OVERRIDE
+      REQUIRES_SHARED(Locks::mutator_lock_)
+      REQUIRES(!Locks::dex_lock_);
+};
+}  // namespace art
+
+#endif  // ART_RUNTIME_AOT_CLASS_LINKER_H_
diff --git a/runtime/art_method-inl.h b/runtime/art_method-inl.h
index 9a9f125..fad9278 100644
--- a/runtime/art_method-inl.h
+++ b/runtime/art_method-inl.h
@@ -154,20 +154,22 @@
   return GetDexCacheResolvedMethods(pointer_size) == other_cache;
 }
 
-inline mirror::Class* ArtMethod::GetClassFromTypeIndex(dex::TypeIndex type_idx, bool resolve) {
-  // TODO: Refactor this function into two functions, Resolve...() and Lookup...()
-  // so that we can properly annotate it with no-suspension possible / suspension possible.
+inline ObjPtr<mirror::Class> ArtMethod::LookupResolvedClassFromTypeIndex(dex::TypeIndex type_idx) {
   ObjPtr<mirror::DexCache> dex_cache = GetDexCache();
   ObjPtr<mirror::Class> type = dex_cache->GetResolvedType(type_idx);
   if (UNLIKELY(type == nullptr)) {
-    ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
-    if (resolve) {
-      type = class_linker->ResolveType(type_idx, this);
-      CHECK(type != nullptr || Thread::Current()->IsExceptionPending());
-    } else {
-      type = class_linker->LookupResolvedType(
-          *dex_cache->GetDexFile(), type_idx, dex_cache, GetClassLoader());
-    }
+    type = Runtime::Current()->GetClassLinker()->LookupResolvedType(
+        *dex_cache->GetDexFile(), type_idx, dex_cache, GetClassLoader());
+  }
+  return type.Ptr();
+}
+
+inline ObjPtr<mirror::Class> ArtMethod::ResolveClassFromTypeIndex(dex::TypeIndex type_idx) {
+  ObjPtr<mirror::DexCache> dex_cache = GetDexCache();
+  ObjPtr<mirror::Class> type = dex_cache->GetResolvedType(type_idx);
+  if (UNLIKELY(type == nullptr)) {
+    type = Runtime::Current()->GetClassLinker()->ResolveType(type_idx, this);
+    CHECK(type != nullptr || Thread::Current()->IsExceptionPending());
   }
   return type.Ptr();
 }
@@ -294,7 +296,7 @@
 
 inline bool ArtMethod::IsResolvedTypeIdx(dex::TypeIndex type_idx) {
   DCHECK(!IsProxyMethod());
-  return GetClassFromTypeIndex(type_idx, /* resolve */ false) != nullptr;
+  return LookupResolvedClassFromTypeIndex(type_idx) != nullptr;
 }
 
 inline int32_t ArtMethod::GetLineNumFromDexPC(uint32_t dex_pc) {
@@ -403,13 +405,20 @@
                    pointer_size);
 }
 
-inline mirror::Class* ArtMethod::GetReturnType(bool resolve) {
+inline dex::TypeIndex ArtMethod::GetReturnTypeIndex() {
   DCHECK(!IsProxyMethod());
   const DexFile* dex_file = GetDexFile();
   const DexFile::MethodId& method_id = dex_file->GetMethodId(GetDexMethodIndex());
   const DexFile::ProtoId& proto_id = dex_file->GetMethodPrototype(method_id);
-  dex::TypeIndex return_type_idx = proto_id.return_type_idx_;
-  return GetClassFromTypeIndex(return_type_idx, resolve);
+  return proto_id.return_type_idx_;
+}
+
+inline ObjPtr<mirror::Class> ArtMethod::LookupResolvedReturnType() {
+  return LookupResolvedClassFromTypeIndex(GetReturnTypeIndex());
+}
+
+inline ObjPtr<mirror::Class> ArtMethod::ResolveReturnType() {
+  return ResolveClassFromTypeIndex(GetReturnTypeIndex());
 }
 
 inline bool ArtMethod::HasSingleImplementation() {
diff --git a/runtime/art_method.cc b/runtime/art_method.cc
index 631f5e7..7d8deda 100644
--- a/runtime/art_method.cc
+++ b/runtime/art_method.cc
@@ -280,7 +280,7 @@
       break;
     }
     // Does this catch exception type apply?
-    mirror::Class* iter_exception_type = GetClassFromTypeIndex(iter_type_idx, true /* resolve */);
+    ObjPtr<mirror::Class> iter_exception_type = ResolveClassFromTypeIndex(iter_type_idx);
     if (UNLIKELY(iter_exception_type == nullptr)) {
       // Now have a NoClassDefFoundError as exception. Ignore in case the exception class was
       // removed by a pro-guard like tool.
diff --git a/runtime/art_method.h b/runtime/art_method.h
index 511ac83..cac40e0 100644
--- a/runtime/art_method.h
+++ b/runtime/art_method.h
@@ -376,8 +376,11 @@
                                       PointerSize pointer_size)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
-  // Get the Class* from the type index into this method's dex cache.
-  mirror::Class* GetClassFromTypeIndex(dex::TypeIndex type_idx, bool resolve)
+  // Lookup the Class* from the type index into this method's dex cache.
+  ObjPtr<mirror::Class> LookupResolvedClassFromTypeIndex(dex::TypeIndex type_idx)
+      REQUIRES_SHARED(Locks::mutator_lock_);
+  // Resolve the Class* from the type index into this method's dex cache.
+  ObjPtr<mirror::Class> ResolveClassFromTypeIndex(dex::TypeIndex type_idx)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
   // Returns true if this method has the same name and signature of the other method.
@@ -592,9 +595,11 @@
   const char* GetTypeDescriptorFromTypeIdx(dex::TypeIndex type_idx)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
-  // May cause thread suspension due to GetClassFromTypeIdx calling ResolveType this caused a large
-  // number of bugs at call sites.
-  mirror::Class* GetReturnType(bool resolve) REQUIRES_SHARED(Locks::mutator_lock_);
+  // Lookup return type.
+  ObjPtr<mirror::Class> LookupResolvedReturnType() REQUIRES_SHARED(Locks::mutator_lock_);
+  // Resolve return type. May cause thread suspension due to GetClassFromTypeIdx
+  // calling ResolveType this caused a large number of bugs at call sites.
+  ObjPtr<mirror::Class> ResolveReturnType() REQUIRES_SHARED(Locks::mutator_lock_);
 
   mirror::ClassLoader* GetClassLoader() REQUIRES_SHARED(Locks::mutator_lock_);
 
@@ -748,6 +753,8 @@
   // Compare given pointer size to the image pointer size.
   static bool IsImagePointerSize(PointerSize pointer_size);
 
+  dex::TypeIndex GetReturnTypeIndex() REQUIRES_SHARED(Locks::mutator_lock_);
+
   template<typename T>
   ALWAYS_INLINE T GetNativePointer(MemberOffset offset, PointerSize pointer_size) const {
     static_assert(std::is_pointer<T>::value, "T must be a pointer type");
diff --git a/runtime/base/unix_file/fd_file.cc b/runtime/base/unix_file/fd_file.cc
index 0c73ce7..eb8ced0 100644
--- a/runtime/base/unix_file/fd_file.cc
+++ b/runtime/base/unix_file/fd_file.cc
@@ -438,4 +438,30 @@
   return true;
 }
 
+int FdFile::Compare(FdFile* other) {
+  int64_t length = GetLength();
+  int64_t length2 = other->GetLength();
+  if (length != length2) {
+    return length < length2 ? -1 : 1;
+  }
+  static const size_t kBufferSize = 4096;
+  std::unique_ptr<uint8_t[]> buffer1(new uint8_t[kBufferSize]);
+  std::unique_ptr<uint8_t[]> buffer2(new uint8_t[kBufferSize]);
+  while (length > 0) {
+    size_t len = std::min(kBufferSize, static_cast<size_t>(length));
+    if (!ReadFully(&buffer1[0], len)) {
+      return -1;
+    }
+    if (!other->ReadFully(&buffer2[0], len)) {
+      return 1;
+    }
+    int result = memcmp(&buffer1[0], &buffer2[0], len);
+    if (result != 0) {
+      return result;
+    }
+    length -= len;
+  }
+  return 0;
+}
+
 }  // namespace unix_file
diff --git a/runtime/base/unix_file/fd_file.h b/runtime/base/unix_file/fd_file.h
index e07c3fd..91b08bc 100644
--- a/runtime/base/unix_file/fd_file.h
+++ b/runtime/base/unix_file/fd_file.h
@@ -145,6 +145,11 @@
   // WARNING: Only use this when you know what you're doing!
   void MarkUnchecked();
 
+  // Compare against another file. Returns 0 if the files are equivalent, otherwise returns -1 or 1
+  // depending on if the lenghts are different. If the lengths are the same, the function returns
+  // the difference of the first byte that differs.
+  int Compare(FdFile* other);
+
  protected:
   // If the guard state indicates checking (!=kNoCheck), go to the target state "target". Print the
   // given warning if the current state is or exceeds warn_threshold.
diff --git a/runtime/base/unix_file/fd_file_test.cc b/runtime/base/unix_file/fd_file_test.cc
index 6aef348..8b1a115 100644
--- a/runtime/base/unix_file/fd_file_test.cc
+++ b/runtime/base/unix_file/fd_file_test.cc
@@ -220,4 +220,58 @@
   EXPECT_FALSE(art::OS::FileExists(filename.c_str())) << filename;
 }
 
+TEST_F(FdFileTest, Compare) {
+  std::vector<uint8_t> buffer;
+  constexpr int64_t length = 17 * art::KB;
+  for (size_t i = 0; i < length; ++i) {
+    buffer.push_back(static_cast<uint8_t>(i));
+  }
+
+  auto reset_compare = [&](art::ScratchFile& a, art::ScratchFile& b) {
+    a.GetFile()->ResetOffset();
+    b.GetFile()->ResetOffset();
+    return a.GetFile()->Compare(b.GetFile());
+  };
+
+  art::ScratchFile tmp;
+  EXPECT_TRUE(tmp.GetFile()->WriteFully(&buffer[0], length));
+  EXPECT_EQ(tmp.GetFile()->GetLength(), length);
+
+  art::ScratchFile tmp2;
+  EXPECT_TRUE(tmp2.GetFile()->WriteFully(&buffer[0], length));
+  EXPECT_EQ(tmp2.GetFile()->GetLength(), length);
+
+  // Basic equality check.
+  tmp.GetFile()->ResetOffset();
+  tmp2.GetFile()->ResetOffset();
+  // Files should be the same.
+  EXPECT_EQ(reset_compare(tmp, tmp2), 0);
+
+  // Change a byte near the start.
+  ++buffer[2];
+  art::ScratchFile tmp3;
+  EXPECT_TRUE(tmp3.GetFile()->WriteFully(&buffer[0], length));
+  --buffer[2];
+  EXPECT_NE(reset_compare(tmp, tmp3), 0);
+
+  // Change a byte near the middle.
+  ++buffer[length / 2];
+  art::ScratchFile tmp4;
+  EXPECT_TRUE(tmp4.GetFile()->WriteFully(&buffer[0], length));
+  --buffer[length / 2];
+  EXPECT_NE(reset_compare(tmp, tmp4), 0);
+
+  // Change a byte near the end.
+  ++buffer[length - 5];
+  art::ScratchFile tmp5;
+  EXPECT_TRUE(tmp5.GetFile()->WriteFully(&buffer[0], length));
+  --buffer[length - 5];
+  EXPECT_NE(reset_compare(tmp, tmp5), 0);
+
+  // Reference check
+  art::ScratchFile tmp6;
+  EXPECT_TRUE(tmp6.GetFile()->WriteFully(&buffer[0], length));
+  EXPECT_EQ(reset_compare(tmp, tmp6), 0);
+}
+
 }  // namespace unix_file
diff --git a/runtime/class_linker-inl.h b/runtime/class_linker-inl.h
index 0096c37..439ecaf 100644
--- a/runtime/class_linker-inl.h
+++ b/runtime/class_linker-inl.h
@@ -312,6 +312,17 @@
   return klass.Ptr();
 }
 
+template <class Visitor>
+inline void ClassLinker::VisitClassTables(const Visitor& visitor) {
+  Thread* const self = Thread::Current();
+  WriterMutexLock mu(self, *Locks::classlinker_classes_lock_);
+  for (const ClassLoaderData& data : class_loaders_) {
+    if (data.class_table != nullptr) {
+      visitor(data.class_table);
+    }
+  }
+}
+
 }  // namespace art
 
 #endif  // ART_RUNTIME_CLASS_LINKER_INL_H_
diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc
index e5ed8ae..3ac87c5 100644
--- a/runtime/class_linker.cc
+++ b/runtime/class_linker.cc
@@ -3555,6 +3555,14 @@
   data.resolved_methods = dex_cache->GetResolvedMethods();
   data.class_table = ClassTableForClassLoader(class_loader);
   DCHECK(data.class_table != nullptr);
+  // Make sure to hold the dex cache live in the class table. This case happens for the boot class
+  // path dex caches without an image.
+  data.class_table->InsertStrongRoot(dex_cache);
+  if (class_loader != nullptr) {
+    // Since we added a strong root to the class table, do the write barrier as required for
+    // remembered sets and generational GCs.
+    Runtime::Current()->GetHeap()->WriteBarrierEveryFieldOf(class_loader);
+  }
   dex_caches_.push_back(data);
 }
 
@@ -4712,7 +4720,7 @@
   CHECK_STREQ(np->GetName(), prototype->GetName());
   CHECK_STREQ(np->GetShorty(), prototype->GetShorty());
   // More complex sanity - via dex cache
-  CHECK_EQ(np->GetReturnType(true /* resolve */), prototype->GetReturnType(true /* resolve */));
+  CHECK_EQ(np->ResolveReturnType(), prototype->ResolveReturnType());
 }
 
 bool ClassLinker::CanWeInitializeClass(ObjPtr<mirror::Class> klass, bool can_init_statics,
@@ -4852,11 +4860,16 @@
       return WaitForInitializeClass(klass, self, lock);
     }
 
+    // Try to get the oat class's status for this class if the oat file is present. The compiler
+    // tries to validate superclass descriptors, and writes the result into the oat file.
+    // Runtime correctness is guaranteed by classpath checks done on loading. If the classpath
+    // is different at runtime than it was at compile time, the oat file is rejected. So if the
+    // oat file is present, the classpaths must match, and the runtime time check can be skipped.
     bool has_oat_class = false;
-    const OatFile::OatClass oat_class =
-        (Runtime::Current()->IsStarted() && !Runtime::Current()->IsAotCompiler())
-            ? OatFile::FindOatClass(klass->GetDexFile(), klass->GetDexClassDefIndex(), &has_oat_class)
-            : OatFile::OatClass::Invalid();
+    const Runtime* runtime = Runtime::Current();
+    const OatFile::OatClass oat_class = (runtime->IsStarted() && !runtime->IsAotCompiler())
+        ? OatFile::FindOatClass(klass->GetDexFile(), klass->GetDexClassDefIndex(), &has_oat_class)
+        : OatFile::OatClass::Invalid();
     if (oat_class.GetStatus() < mirror::Class::kStatusSuperclassValidated &&
         !ValidateSuperClassDescriptors(klass)) {
       mirror::Class::SetStatus(klass, mirror::Class::kStatusErrorResolved, self);
@@ -4887,7 +4900,9 @@
       if (!super_initialized) {
         // The super class was verified ahead of entering initializing, we should only be here if
         // the super class became erroneous due to initialization.
-        CHECK(handle_scope_super->IsErroneous() && self->IsExceptionPending())
+        // For the case of aot compiler, the super class might also be initializing but we don't
+        // want to process circular dependencies in pre-compile.
+        CHECK(self->IsExceptionPending())
             << "Super class initialization failed for "
             << handle_scope_super->PrettyDescriptor()
             << " that has unexpected status " << handle_scope_super->GetStatus()
@@ -5188,12 +5203,12 @@
     REQUIRES_SHARED(Locks::mutator_lock_) {
   {
     StackHandleScope<1> hs(self);
-    Handle<mirror::Class> return_type(hs.NewHandle(method1->GetReturnType(true /* resolve */)));
+    Handle<mirror::Class> return_type(hs.NewHandle(method1->ResolveReturnType()));
     if (UNLIKELY(return_type == nullptr)) {
       ThrowSignatureCheckResolveReturnTypeException(klass, super_klass, method1, method1);
       return false;
     }
-    ObjPtr<mirror::Class> other_return_type = method2->GetReturnType(true /* resolve */);
+    ObjPtr<mirror::Class> other_return_type = method2->ResolveReturnType();
     if (UNLIKELY(other_return_type == nullptr)) {
       ThrowSignatureCheckResolveReturnTypeException(klass, super_klass, method1, method2);
       return false;
@@ -5238,7 +5253,7 @@
     StackHandleScope<1> hs(self);
     dex::TypeIndex param_type_idx = types1->GetTypeItem(i).type_idx_;
     Handle<mirror::Class> param_type(hs.NewHandle(
-        method1->GetClassFromTypeIndex(param_type_idx, true /* resolve */)));
+        method1->ResolveClassFromTypeIndex(param_type_idx)));
     if (UNLIKELY(param_type == nullptr)) {
       ThrowSignatureCheckResolveArgException(klass, super_klass, method1,
                                              method1, i, param_type_idx);
@@ -5246,7 +5261,7 @@
     }
     dex::TypeIndex other_param_type_idx = types2->GetTypeItem(i).type_idx_;
     ObjPtr<mirror::Class> other_param_type =
-        method2->GetClassFromTypeIndex(other_param_type_idx, true /* resolve */);
+        method2->ResolveClassFromTypeIndex(other_param_type_idx);
     if (UNLIKELY(other_param_type == nullptr)) {
       ThrowSignatureCheckResolveArgException(klass, super_klass, method1,
                                              method2, i, other_param_type_idx);
@@ -8510,7 +8525,7 @@
     it.Next();
   }
 
-  Handle<mirror::Class> return_type = hs.NewHandle(target_method->GetReturnType(true));
+  Handle<mirror::Class> return_type = hs.NewHandle(target_method->ResolveReturnType());
   if (UNLIKELY(return_type.IsNull())) {
     DCHECK(self->IsExceptionPending());
     return nullptr;
@@ -8587,7 +8602,7 @@
   if (!method->IsNative()) {
     method->SetEntryPointFromQuickCompiledCode(GetQuickToInterpreterBridge());
   } else {
-    SetEntryPointsToCompiledCode(method, GetQuickGenericJniStub());
+    method->SetEntryPointFromQuickCompiledCode(GetQuickGenericJniStub());
   }
 }
 
@@ -8839,16 +8854,6 @@
   find_array_class_cache_next_victim_ = 0;
 }
 
-void ClassLinker::ClearClassTableStrongRoots() const {
-  Thread* const self = Thread::Current();
-  WriterMutexLock mu(self, *Locks::classlinker_classes_lock_);
-  for (const ClassLoaderData& data : class_loaders_) {
-    if (data.class_table != nullptr) {
-      data.class_table->ClearStrongRoots();
-    }
-  }
-}
-
 void ClassLinker::VisitClassLoaders(ClassLoaderVisitor* visitor) const {
   Thread* const self = Thread::Current();
   for (const ClassLoaderData& data : class_loaders_) {
diff --git a/runtime/class_linker.h b/runtime/class_linker.h
index 62fb45b..bf14aeb 100644
--- a/runtime/class_linker.h
+++ b/runtime/class_linker.h
@@ -141,7 +141,7 @@
   };
 
   explicit ClassLinker(InternTable* intern_table);
-  ~ClassLinker();
+  virtual ~ClassLinker();
 
   // Initialize class linker by bootstraping from dex files.
   bool InitWithoutImage(std::vector<std::unique_ptr<const DexFile>> boot_class_path,
@@ -635,9 +635,9 @@
   // Create the IMT and conflict tables for a class.
   void FillIMTAndConflictTables(ObjPtr<mirror::Class> klass) REQUIRES_SHARED(Locks::mutator_lock_);
 
-  // Clear class table strong roots (other than classes themselves). This is done by dex2oat to
-  // allow pruning dex caches.
-  void ClearClassTableStrongRoots() const
+  // Visit all of the class tables. This is used by dex2oat to allow pruning dex caches.
+  template <class Visitor>
+  void VisitClassTables(const Visitor& visitor)
       REQUIRES(!Locks::classlinker_classes_lock_)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
@@ -702,6 +702,14 @@
     ClassTable* class_table;
   };
 
+ protected:
+  virtual bool InitializeClass(Thread* self,
+                               Handle<mirror::Class> klass,
+                               bool can_run_clinit,
+                               bool can_init_parents)
+      REQUIRES_SHARED(Locks::mutator_lock_)
+      REQUIRES(!Locks::dex_lock_);
+
  private:
   class LinkInterfaceMethodsHelper;
 
@@ -891,12 +899,6 @@
       REQUIRES(!Locks::dex_lock_)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
-  bool InitializeClass(Thread* self,
-                       Handle<mirror::Class> klass,
-                       bool can_run_clinit,
-                       bool can_init_parents)
-      REQUIRES_SHARED(Locks::mutator_lock_)
-      REQUIRES(!Locks::dex_lock_);
   bool InitializeDefaultInterfaceRecursive(Thread* self,
                                            Handle<mirror::Class> klass,
                                            bool can_run_clinit,
diff --git a/runtime/class_loader_context.cc b/runtime/class_loader_context.cc
index ff440d7..e7051b3 100644
--- a/runtime/class_loader_context.cc
+++ b/runtime/class_loader_context.cc
@@ -68,6 +68,10 @@
   }
 }
 
+std::unique_ptr<ClassLoaderContext> ClassLoaderContext::Default() {
+  return Create("");
+}
+
 std::unique_ptr<ClassLoaderContext> ClassLoaderContext::Create(const std::string& spec) {
   std::unique_ptr<ClassLoaderContext> result(new ClassLoaderContext());
   if (result->Parse(spec)) {
@@ -263,7 +267,16 @@
   return removed_locations;
 }
 
+std::string ClassLoaderContext::EncodeContextForDex2oat(const std::string& base_dir) const {
+  return EncodeContext(base_dir, /*for_dex2oat*/ true);
+}
+
 std::string ClassLoaderContext::EncodeContextForOatFile(const std::string& base_dir) const {
+  return EncodeContext(base_dir, /*for_dex2oat*/ false);
+}
+
+std::string ClassLoaderContext::EncodeContext(const std::string& base_dir,
+                                              bool for_dex2oat) const {
   CheckDexFilesOpened("EncodeContextForOatFile");
   if (special_shared_library_) {
     return OatFile::kSpecialSharedLibrary;
@@ -286,8 +299,17 @@
     }
     out << GetClassLoaderTypeName(info.type);
     out << kClassLoaderOpeningMark;
+    std::set<std::string> seen_locations;
     for (size_t k = 0; k < info.opened_dex_files.size(); k++) {
       const std::unique_ptr<const DexFile>& dex_file = info.opened_dex_files[k];
+      if (for_dex2oat) {
+        // dex2oat only needs the base location. It cannot accept multidex locations.
+        // So ensure we only add each file once.
+        bool new_insert = seen_locations.insert(dex_file->GetBaseLocation()).second;
+        if (!new_insert) {
+          continue;
+        }
+      }
       const std::string& location = dex_file->GetLocation();
       if (k > 0) {
         out << kClasspathSeparator;
@@ -298,8 +320,11 @@
       } else {
         out << dex_file->GetLocation().c_str();
       }
-      out << kDexFileChecksumSeparator;
-      out << dex_file->GetLocationChecksum();
+      // dex2oat does not need the checksums.
+      if (!for_dex2oat) {
+        out << kDexFileChecksumSeparator;
+        out << dex_file->GetLocationChecksum();
+      }
     }
     out << kClassLoaderClosingMark;
   }
@@ -593,7 +618,7 @@
   }
 }
 
-bool ClassLoaderContext::VerifyClassLoaderContextMatch(const std::string& context_spec) {
+bool ClassLoaderContext::VerifyClassLoaderContextMatch(const std::string& context_spec) const {
   ClassLoaderContext expected_context;
   if (!expected_context.Parse(context_spec, /*parse_checksums*/ true)) {
     LOG(WARNING) << "Invalid class loader context: " << context_spec;
@@ -607,7 +632,8 @@
   if (expected_context.class_loader_chain_.size() != class_loader_chain_.size()) {
     LOG(WARNING) << "ClassLoaderContext size mismatch. expected="
         << expected_context.class_loader_chain_.size()
-        << ", actual=" << class_loader_chain_.size();
+        << ", actual=" << class_loader_chain_.size()
+        << " (" << context_spec << " | " << EncodeContextForOatFile("") << ")";
     return false;
   }
 
@@ -617,13 +643,15 @@
     if (info.type != expected_info.type) {
       LOG(WARNING) << "ClassLoaderContext type mismatch for position " << i
           << ". expected=" << GetClassLoaderTypeName(expected_info.type)
-          << ", found=" << GetClassLoaderTypeName(info.type);
+          << ", found=" << GetClassLoaderTypeName(info.type)
+          << " (" << context_spec << " | " << EncodeContextForOatFile("") << ")";
       return false;
     }
     if (info.classpath.size() != expected_info.classpath.size()) {
       LOG(WARNING) << "ClassLoaderContext classpath size mismatch for position " << i
             << ". expected=" << expected_info.classpath.size()
-            << ", found=" << info.classpath.size();
+            << ", found=" << info.classpath.size()
+            << " (" << context_spec << " | " << EncodeContextForOatFile("") << ")";
       return false;
     }
 
@@ -634,13 +662,15 @@
       if (info.classpath[k] != expected_info.classpath[k]) {
         LOG(WARNING) << "ClassLoaderContext classpath element mismatch for position " << i
             << ". expected=" << expected_info.classpath[k]
-            << ", found=" << info.classpath[k];
+            << ", found=" << info.classpath[k]
+            << " (" << context_spec << " | " << EncodeContextForOatFile("") << ")";
         return false;
       }
       if (info.checksums[k] != expected_info.checksums[k]) {
         LOG(WARNING) << "ClassLoaderContext classpath element checksum mismatch for position " << i
             << ". expected=" << expected_info.checksums[k]
-            << ", found=" << info.checksums[k];
+            << ", found=" << info.checksums[k]
+            << " (" << context_spec << " | " << EncodeContextForOatFile("") << ")";
         return false;
       }
     }
diff --git a/runtime/class_loader_context.h b/runtime/class_loader_context.h
index 37dd02b..9afa880 100644
--- a/runtime/class_loader_context.h
+++ b/runtime/class_loader_context.h
@@ -34,9 +34,6 @@
 // Utility class which holds the class loader context used during compilation/verification.
 class ClassLoaderContext {
  public:
-  // Creates an empty context (with no class loaders).
-  ClassLoaderContext();
-
   ~ClassLoaderContext();
 
   // Opens requested class path files and appends them to ClassLoaderInfo::opened_dex_files.
@@ -82,9 +79,16 @@
   // (so that it can be read and verified at runtime against the actual class
   // loader hierarchy).
   // Should only be called if OpenDexFiles() returned true.
-  // E.g. if the context is PCL[a.dex:b.dex] this will return "a.dex*a_checksum*b.dex*a_checksum".
+  // E.g. if the context is PCL[a.dex:b.dex] this will return
+  // "PCL[a.dex*a_checksum*b.dex*a_checksum]".
   std::string EncodeContextForOatFile(const std::string& base_dir) const;
 
+  // Encodes the context as a string suitable to be passed to dex2oat.
+  // This is the same as EncodeContextForOatFile but without adding the checksums
+  // and only adding each dex files once (no multidex).
+  // Should only be called if OpenDexFiles() returned true.
+  std::string EncodeContextForDex2oat(const std::string& base_dir) const;
+
   // Flattens the opened dex files into the given vector.
   // Should only be called if OpenDexFiles() returned true.
   std::vector<const DexFile*> FlattenOpenedDexFiles() const;
@@ -94,7 +98,7 @@
   //    - the number and type of the class loaders from the chain matches
   //    - the class loader from the same position have the same classpath
   //      (the order and checksum of the dex files matches)
-  bool VerifyClassLoaderContextMatch(const std::string& context_spec);
+  bool VerifyClassLoaderContextMatch(const std::string& context_spec) const;
 
   // Creates the class loader context from the given string.
   // The format: ClassLoaderType1[ClasspathElem1:ClasspathElem2...];ClassLoaderType2[...]...
@@ -119,6 +123,10 @@
   static std::unique_ptr<ClassLoaderContext> CreateContextForClassLoader(jobject class_loader,
                                                                          jobjectArray dex_elements);
 
+  // Returns the default class loader context to be used when none is specified.
+  // This will return a context with a single and empty PathClassLoader.
+  static std::unique_ptr<ClassLoaderContext> Default();
+
  private:
   enum ClassLoaderType {
     kInvalidClassLoader = 0,
@@ -144,6 +152,9 @@
     explicit ClassLoaderInfo(ClassLoaderType cl_type) : type(cl_type) {}
   };
 
+  // Creates an empty context (with no class loaders).
+  ClassLoaderContext();
+
   // Constructs an empty context.
   // `owns_the_dex_files` specifies whether or not the context will own the opened dex files
   // present in the class loader chain. If `owns_the_dex_files` is true then OpenDexFiles cannot
@@ -173,7 +184,15 @@
   bool AddInfoToContextFromClassLoader(ScopedObjectAccessAlreadyRunnable& soa,
                                        Handle<mirror::ClassLoader> class_loader,
                                        Handle<mirror::ObjectArray<mirror::Object>> dex_elements)
-  REQUIRES_SHARED(Locks::mutator_lock_);
+    REQUIRES_SHARED(Locks::mutator_lock_);
+
+  // Encodes the context as a string suitable to be passed to dex2oat or to be added to the
+  // oat file as the class path key.
+  // If for_dex2oat is true, the encoding adds each file once (i.e. it does not add multidex
+  // location). Otherwise, for oat files, the encoding adds all the dex files (including multidex)
+  // together with their checksums.
+  // Should only be called if OpenDexFiles() returned true.
+  std::string EncodeContext(const std::string& base_dir, bool for_dex2oat) const;
 
   // Extracts the class loader type from the given spec.
   // Return ClassLoaderContext::kInvalidClassLoader if the class loader type is not
diff --git a/runtime/class_loader_context_test.cc b/runtime/class_loader_context_test.cc
index 2b85188..d4688c1 100644
--- a/runtime/class_loader_context_test.cc
+++ b/runtime/class_loader_context_test.cc
@@ -455,6 +455,20 @@
   ASSERT_EQ(expected_encoding, context->EncodeContextForOatFile(""));
 }
 
+TEST_F(ClassLoaderContextTest, EncodeForDex2oat) {
+  std::string dex1_name = GetTestDexFileName("Main");
+  std::string dex2_name = GetTestDexFileName("MultiDex");
+  std::unique_ptr<ClassLoaderContext> context =
+      ClassLoaderContext::Create("PCL[" + dex1_name + ":" + dex2_name + "]");
+  ASSERT_TRUE(context->OpenDexFiles(InstructionSet::kArm, ""));
+
+  std::vector<std::unique_ptr<const DexFile>> dex1 = OpenTestDexFiles("Main");
+  std::vector<std::unique_ptr<const DexFile>> dex2 = OpenTestDexFiles("MultiDex");
+  std::string encoding = context->EncodeContextForDex2oat("");
+  std::string expected_encoding = "PCL[" + dex1_name + ":" + dex2_name + "]";
+  ASSERT_EQ(expected_encoding, context->EncodeContextForDex2oat(""));
+}
+
 // TODO(calin) add a test which creates the context for a class loader together with dex_elements.
 TEST_F(ClassLoaderContextTest, CreateContextForClassLoader) {
   // The chain is
diff --git a/runtime/class_table-inl.h b/runtime/class_table-inl.h
index b15d82f..1280466 100644
--- a/runtime/class_table-inl.h
+++ b/runtime/class_table-inl.h
@@ -132,6 +132,13 @@
   }
 }
 
+template <typename Filter>
+inline void ClassTable::RemoveStrongRoots(const Filter& filter) {
+  WriterMutexLock mu(Thread::Current(), lock_);
+  strong_roots_.erase(std::remove_if(strong_roots_.begin(), strong_roots_.end(), filter),
+                      strong_roots_.end());
+}
+
 }  // namespace art
 
 #endif  // ART_RUNTIME_CLASS_TABLE_INL_H_
diff --git a/runtime/class_table.h b/runtime/class_table.h
index 8616dfb..a259725 100644
--- a/runtime/class_table.h
+++ b/runtime/class_table.h
@@ -250,6 +250,12 @@
       REQUIRES(!lock_)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
+  // Filter strong roots (other than classes themselves).
+  template <typename Filter>
+  void RemoveStrongRoots(const Filter& filter)
+      REQUIRES(!lock_)
+      REQUIRES_SHARED(Locks::mutator_lock_);
+
   ReaderWriterMutex& GetLock() {
     return lock_;
   }
diff --git a/runtime/common_dex_operations.h b/runtime/common_dex_operations.h
index 528db96..fcc5393 100644
--- a/runtime/common_dex_operations.h
+++ b/runtime/common_dex_operations.h
@@ -200,6 +200,11 @@
       break;
     }
   }
+  if (transaction_active) {
+    if (UNLIKELY(self->IsExceptionPending())) {
+      return false;
+    }
+  }
   return true;
 }
 
diff --git a/runtime/debugger.cc b/runtime/debugger.cc
index 5a87ae8..0b7af4e 100644
--- a/runtime/debugger.cc
+++ b/runtime/debugger.cc
@@ -4008,8 +4008,8 @@
 
         if (shorty[i + 1] == 'L') {
           // Did we really get an argument of an appropriate reference type?
-          mirror::Class* parameter_type =
-              m->GetClassFromTypeIndex(types->GetTypeItem(i).type_idx_, true /* resolve */);
+          ObjPtr<mirror::Class> parameter_type =
+              m->ResolveClassFromTypeIndex(types->GetTypeItem(i).type_idx_);
           mirror::Object* argument = gRegistry->Get<mirror::Object*>(arg_values[i], &error);
           if (error != JDWP::ERR_NONE) {
             return JDWP::ERR_INVALID_OBJECT;
diff --git a/runtime/dex_file_annotations.cc b/runtime/dex_file_annotations.cc
index 2b81f0a..4225ab9 100644
--- a/runtime/dex_file_annotations.cc
+++ b/runtime/dex_file_annotations.cc
@@ -695,8 +695,7 @@
   if (annotation_method == nullptr) {
     return nullptr;
   }
-  Handle<mirror::Class> method_return(hs.NewHandle(
-      annotation_method->GetReturnType(true /* resolve */)));
+  Handle<mirror::Class> method_return(hs.NewHandle(annotation_method->ResolveReturnType()));
 
   DexFile::AnnotationValue annotation_value;
   if (!ProcessAnnotationValue<false>(klass,
@@ -762,15 +761,16 @@
     }
     const uint8_t* annotation = annotation_item->annotation_;
     uint32_t type_index = DecodeUnsignedLeb128(&annotation);
+    ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
+    Thread* self = Thread::Current();
     mirror::Class* resolved_class;
     if (lookup_in_resolved_boot_classes) {
+      // Note: We cannot use ClassLinker::LookupResolvedType() because the current DexCache
+      // may not be registered with the boot class path ClassLoader and we must not pollute
+      // the DexCache with classes that are not in the associated ClassLoader's ClassTable.
+      const char* descriptor = dex_file.StringByTypeIdx(dex::TypeIndex(type_index));
       ObjPtr<mirror::Class> looked_up_class =
-          Runtime::Current()->GetClassLinker()->LookupResolvedType(
-              klass.GetDexFile(),
-              dex::TypeIndex(type_index),
-              klass.GetDexCache(),
-              // Force the use of the bootstrap class loader.
-              static_cast<mirror::ClassLoader*>(nullptr));
+          class_linker->LookupClass(self, descriptor, /* class_loader */ nullptr);
       resolved_class = looked_up_class.Ptr();
       if (resolved_class == nullptr) {
         // If `resolved_class` is null, this is fine: just ignore that
@@ -779,8 +779,8 @@
         continue;
       }
     } else {
-      StackHandleScope<2> hs(Thread::Current());
-      resolved_class = Runtime::Current()->GetClassLinker()->ResolveType(
+      StackHandleScope<2> hs(self);
+      resolved_class = class_linker->ResolveType(
           klass.GetDexFile(),
           dex::TypeIndex(type_index),
           hs.NewHandle(klass.GetDexCache()),
@@ -789,8 +789,8 @@
         std::string temp;
         LOG(WARNING) << StringPrintf("Unable to resolve %s annotation class %d",
                                      klass.GetRealClass()->GetDescriptor(&temp), type_index);
-        CHECK(Thread::Current()->IsExceptionPending());
-        Thread::Current()->ClearException();
+        CHECK(self->IsExceptionPending());
+        self->ClearException();
         continue;
       }
     }
@@ -1073,7 +1073,7 @@
   }
   DexFile::AnnotationValue annotation_value;
   StackHandleScope<1> hs(Thread::Current());
-  Handle<mirror::Class> return_type(hs.NewHandle(method->GetReturnType(true /* resolve */)));
+  Handle<mirror::Class> return_type(hs.NewHandle(method->ResolveReturnType()));
   if (!ProcessAnnotationValue<false>(klass,
                                      &annotation,
                                      &annotation_value,
diff --git a/runtime/entrypoints/entrypoint_utils.cc b/runtime/entrypoints/entrypoint_utils.cc
index 01fc9ce..2bf4372 100644
--- a/runtime/entrypoints/entrypoint_utils.cc
+++ b/runtime/entrypoints/entrypoint_utils.cc
@@ -45,7 +45,7 @@
   }
   // Make sure that the result is an instance of the type this method was expected to return.
   ArtMethod* method = self->GetCurrentMethod(nullptr);
-  mirror::Class* return_type = method->GetReturnType(true /* resolve */);
+  ObjPtr<mirror::Class> return_type = method->ResolveReturnType();
 
   if (!o->InstanceOf(return_type)) {
     Runtime::Current()->GetJavaVM()->JniAbortF(nullptr,
@@ -108,7 +108,7 @@
       ArtMethod* interface_method =
           soa.Decode<mirror::Method>(interface_method_jobj)->GetArtMethod();
       // This can cause thread suspension.
-      mirror::Class* result_type = interface_method->GetReturnType(true /* resolve */);
+      ObjPtr<mirror::Class> result_type = interface_method->ResolveReturnType();
       ObjPtr<mirror::Object> result_ref = soa.Decode<mirror::Object>(result);
       JValue result_unboxed;
       if (!UnboxPrimitiveForResult(result_ref.Ptr(), result_type, &result_unboxed)) {
diff --git a/runtime/gc/collector/concurrent_copying.cc b/runtime/gc/collector/concurrent_copying.cc
index 9d672b1..9b24885 100644
--- a/runtime/gc/collector/concurrent_copying.cc
+++ b/runtime/gc/collector/concurrent_copying.cc
@@ -2431,24 +2431,31 @@
       // Non-immune non-moving space. Use the mark bitmap.
       accounting::ContinuousSpaceBitmap* mark_bitmap =
           heap_mark_bitmap_->GetContinuousSpaceBitmap(from_ref);
-      accounting::LargeObjectBitmap* los_bitmap =
-          heap_mark_bitmap_->GetLargeObjectBitmap(from_ref);
-      CHECK(los_bitmap != nullptr) << "LOS bitmap covers the entire address range";
       bool is_los = mark_bitmap == nullptr;
       if (!is_los && mark_bitmap->Test(from_ref)) {
         // Already marked.
         to_ref = from_ref;
-      } else if (is_los && los_bitmap->Test(from_ref)) {
-        // Already marked in LOS.
-        to_ref = from_ref;
       } else {
-        // Not marked.
-        if (IsOnAllocStack(from_ref)) {
-          // If on the allocation stack, it's considered marked.
+        accounting::LargeObjectBitmap* los_bitmap =
+            heap_mark_bitmap_->GetLargeObjectBitmap(from_ref);
+        // We may not have a large object space for dex2oat, don't assume it exists.
+        if (los_bitmap == nullptr) {
+          CHECK(heap_->GetLargeObjectsSpace() == nullptr)
+              << "LOS bitmap covers the entire address range " << from_ref
+              << " " << heap_->DumpSpaces();
+        }
+        if (los_bitmap != nullptr && is_los && los_bitmap->Test(from_ref)) {
+          // Already marked in LOS.
           to_ref = from_ref;
         } else {
           // Not marked.
-          to_ref = nullptr;
+          if (IsOnAllocStack(from_ref)) {
+            // If on the allocation stack, it's considered marked.
+            to_ref = from_ref;
+          } else {
+            // Not marked.
+            to_ref = nullptr;
+          }
         }
       }
     }
diff --git a/runtime/instrumentation.h b/runtime/instrumentation.h
index 90b5def..9969489 100644
--- a/runtime/instrumentation.h
+++ b/runtime/instrumentation.h
@@ -196,6 +196,10 @@
   }
   bool ShouldNotifyMethodEnterExitEvents() const REQUIRES_SHARED(Locks::mutator_lock_);
 
+  bool CanDeoptimize() {
+    return deoptimization_enabled_;
+  }
+
   // Executes everything with interpreter.
   void DeoptimizeEverything(const char* key)
       REQUIRES(Locks::mutator_lock_, Roles::uninterruptible_)
@@ -457,8 +461,7 @@
   // This is used by the debugger to cause a deoptimization of the thread's stack after updating
   // local variable(s).
   void InstrumentThreadStack(Thread* thread)
-      REQUIRES_SHARED(Locks::mutator_lock_)
-      REQUIRES(!Locks::thread_list_lock_);
+      REQUIRES_SHARED(Locks::mutator_lock_);
 
   static size_t ComputeFrameId(Thread* self,
                                size_t frame_depth,
diff --git a/runtime/interpreter/interpreter_common.cc b/runtime/interpreter/interpreter_common.cc
index 85904ee..f8cb243 100644
--- a/runtime/interpreter/interpreter_common.cc
+++ b/runtime/interpreter/interpreter_common.cc
@@ -33,6 +33,7 @@
 #include "reflection.h"
 #include "stack.h"
 #include "thread-inl.h"
+#include "transaction.h"
 #include "well_known_classes.h"
 
 namespace art {
@@ -42,7 +43,8 @@
   ThrowNullPointerExceptionFromDexPC();
 }
 
-template<FindFieldType find_type, Primitive::Type field_type, bool do_access_check>
+template<FindFieldType find_type, Primitive::Type field_type, bool do_access_check,
+         bool transaction_active>
 bool DoFieldGet(Thread* self, ShadowFrame& shadow_frame, const Instruction* inst,
                 uint16_t inst_data) {
   const bool is_static = (find_type == StaticObjectRead) || (find_type == StaticPrimitiveRead);
@@ -57,6 +59,13 @@
   ObjPtr<mirror::Object> obj;
   if (is_static) {
     obj = f->GetDeclaringClass();
+    if (transaction_active) {
+      if (Runtime::Current()->GetTransaction()->ReadConstraint(obj.Ptr(), f)) {
+        Runtime::Current()->AbortTransactionAndThrowAbortError(self, "Can't read static fields of "
+            + obj->PrettyTypeOf() + " since it does not belong to clinit's class.");
+        return false;
+      }
+    }
   } else {
     obj = shadow_frame.GetVRegReference(inst->VRegB_22c(inst_data));
     if (UNLIKELY(obj == nullptr)) {
@@ -102,15 +111,17 @@
 }
 
 // Explicitly instantiate all DoFieldGet functions.
-#define EXPLICIT_DO_FIELD_GET_TEMPLATE_DECL(_find_type, _field_type, _do_check) \
-  template bool DoFieldGet<_find_type, _field_type, _do_check>(Thread* self, \
+#define EXPLICIT_DO_FIELD_GET_TEMPLATE_DECL(_find_type, _field_type, _do_check, _transaction_active) \
+  template bool DoFieldGet<_find_type, _field_type, _do_check, _transaction_active>(Thread* self, \
                                                                ShadowFrame& shadow_frame, \
                                                                const Instruction* inst, \
                                                                uint16_t inst_data)
 
 #define EXPLICIT_DO_FIELD_GET_ALL_TEMPLATE_DECL(_find_type, _field_type)  \
-    EXPLICIT_DO_FIELD_GET_TEMPLATE_DECL(_find_type, _field_type, false);  \
-    EXPLICIT_DO_FIELD_GET_TEMPLATE_DECL(_find_type, _field_type, true);
+    EXPLICIT_DO_FIELD_GET_TEMPLATE_DECL(_find_type, _field_type, false, true);  \
+    EXPLICIT_DO_FIELD_GET_TEMPLATE_DECL(_find_type, _field_type, false, false);  \
+    EXPLICIT_DO_FIELD_GET_TEMPLATE_DECL(_find_type, _field_type, true, true);  \
+    EXPLICIT_DO_FIELD_GET_TEMPLATE_DECL(_find_type, _field_type, true, false);
 
 // iget-XXX
 EXPLICIT_DO_FIELD_GET_ALL_TEMPLATE_DECL(InstancePrimitiveRead, Primitive::kPrimBoolean)
@@ -261,6 +272,14 @@
   ObjPtr<mirror::Object> obj;
   if (is_static) {
     obj = f->GetDeclaringClass();
+    if (transaction_active) {
+      if (Runtime::Current()->GetTransaction()->WriteConstraint(obj.Ptr(), f)) {
+        Runtime::Current()->AbortTransactionAndThrowAbortError(
+            self, "Can't set fields of " + obj->PrettyTypeOf());
+        return false;
+      }
+    }
+
   } else {
     obj = shadow_frame.GetVRegReference(inst->VRegB_22c(inst_data));
     if (UNLIKELY(obj == nullptr)) {
@@ -1070,7 +1089,7 @@
               // Preserve o since it is used below and GetClassFromTypeIndex may cause thread
               // suspension.
               HandleWrapperObjPtr<mirror::Object> h = hs.NewHandleWrapper(&o);
-              arg_type = method->GetClassFromTypeIndex(type_idx, true /* resolve */);
+              arg_type = method->ResolveClassFromTypeIndex(type_idx);
               if (arg_type == nullptr) {
                 CHECK(self->IsExceptionPending());
                 return false;
diff --git a/runtime/interpreter/interpreter_common.h b/runtime/interpreter/interpreter_common.h
index d293aeb..b228e28 100644
--- a/runtime/interpreter/interpreter_common.h
+++ b/runtime/interpreter/interpreter_common.h
@@ -270,7 +270,8 @@
 
 // Handles iget-XXX and sget-XXX instructions.
 // Returns true on success, otherwise throws an exception and returns false.
-template<FindFieldType find_type, Primitive::Type field_type, bool do_access_check>
+template<FindFieldType find_type, Primitive::Type field_type, bool do_access_check,
+         bool transaction_active = false>
 bool DoFieldGet(Thread* self, ShadowFrame& shadow_frame, const Instruction* inst,
                 uint16_t inst_data) REQUIRES_SHARED(Locks::mutator_lock_);
 
diff --git a/runtime/interpreter/interpreter_switch_impl.cc b/runtime/interpreter/interpreter_switch_impl.cc
index 0a2705d..0c5a45f 100644
--- a/runtime/interpreter/interpreter_switch_impl.cc
+++ b/runtime/interpreter/interpreter_switch_impl.cc
@@ -349,7 +349,7 @@
         const size_t ref_idx = inst->VRegA_11x(inst_data);
         ObjPtr<mirror::Object> obj_result = shadow_frame.GetVRegReference(ref_idx);
         if (do_assignability_check && obj_result != nullptr) {
-          ObjPtr<mirror::Class> return_type = method->GetReturnType(true /* resolve */);
+          ObjPtr<mirror::Class> return_type = method->ResolveReturnType();
           // Re-load since it might have moved.
           obj_result = shadow_frame.GetVRegReference(ref_idx);
           if (return_type == nullptr) {
@@ -1313,50 +1313,50 @@
       }
       case Instruction::SGET_BOOLEAN: {
         PREAMBLE();
-        bool success = DoFieldGet<StaticPrimitiveRead, Primitive::kPrimBoolean, do_access_check>(
-            self, shadow_frame, inst, inst_data);
+        bool success = DoFieldGet<StaticPrimitiveRead, Primitive::kPrimBoolean, do_access_check,
+            transaction_active>(self, shadow_frame, inst, inst_data);
         POSSIBLY_HANDLE_PENDING_EXCEPTION(!success, Next_2xx);
         break;
       }
       case Instruction::SGET_BYTE: {
         PREAMBLE();
-        bool success = DoFieldGet<StaticPrimitiveRead, Primitive::kPrimByte, do_access_check>(
-            self, shadow_frame, inst, inst_data);
+        bool success = DoFieldGet<StaticPrimitiveRead, Primitive::kPrimByte, do_access_check,
+            transaction_active>(self, shadow_frame, inst, inst_data);
         POSSIBLY_HANDLE_PENDING_EXCEPTION(!success, Next_2xx);
         break;
       }
       case Instruction::SGET_CHAR: {
         PREAMBLE();
-        bool success = DoFieldGet<StaticPrimitiveRead, Primitive::kPrimChar, do_access_check>(
-            self, shadow_frame, inst, inst_data);
+        bool success = DoFieldGet<StaticPrimitiveRead, Primitive::kPrimChar, do_access_check,
+            transaction_active>(self, shadow_frame, inst, inst_data);
         POSSIBLY_HANDLE_PENDING_EXCEPTION(!success, Next_2xx);
         break;
       }
       case Instruction::SGET_SHORT: {
         PREAMBLE();
-        bool success = DoFieldGet<StaticPrimitiveRead, Primitive::kPrimShort, do_access_check>(
-            self, shadow_frame, inst, inst_data);
+        bool success = DoFieldGet<StaticPrimitiveRead, Primitive::kPrimShort, do_access_check,
+            transaction_active>(self, shadow_frame, inst, inst_data);
         POSSIBLY_HANDLE_PENDING_EXCEPTION(!success, Next_2xx);
         break;
       }
       case Instruction::SGET: {
         PREAMBLE();
-        bool success = DoFieldGet<StaticPrimitiveRead, Primitive::kPrimInt, do_access_check>(
-            self, shadow_frame, inst, inst_data);
+        bool success = DoFieldGet<StaticPrimitiveRead, Primitive::kPrimInt, do_access_check,
+            transaction_active>(self, shadow_frame, inst, inst_data);
         POSSIBLY_HANDLE_PENDING_EXCEPTION(!success, Next_2xx);
         break;
       }
       case Instruction::SGET_WIDE: {
         PREAMBLE();
-        bool success = DoFieldGet<StaticPrimitiveRead, Primitive::kPrimLong, do_access_check>(
-            self, shadow_frame, inst, inst_data);
+        bool success = DoFieldGet<StaticPrimitiveRead, Primitive::kPrimLong, do_access_check,
+            transaction_active>(self, shadow_frame, inst, inst_data);
         POSSIBLY_HANDLE_PENDING_EXCEPTION(!success, Next_2xx);
         break;
       }
       case Instruction::SGET_OBJECT: {
         PREAMBLE();
-        bool success = DoFieldGet<StaticObjectRead, Primitive::kPrimNot, do_access_check>(
-            self, shadow_frame, inst, inst_data);
+        bool success = DoFieldGet<StaticObjectRead, Primitive::kPrimNot, do_access_check,
+            transaction_active>(self, shadow_frame, inst, inst_data);
         POSSIBLY_HANDLE_PENDING_EXCEPTION(!success, Next_2xx);
         break;
       }
diff --git a/runtime/jit/jit_code_cache.cc b/runtime/jit/jit_code_cache.cc
index a030a51..59373eb 100644
--- a/runtime/jit/jit_code_cache.cc
+++ b/runtime/jit/jit_code_cache.cc
@@ -1600,7 +1600,7 @@
     // is the one we expect. We change to the non-obsolete versions in the error message since the
     // obsolete version of the method might not be fully initialized yet. This situation can only
     // occur when we are in the process of allocating and setting up obsolete methods. Otherwise
-    // method and it->second should be identical. (See runtime/openjdkjvmti/ti_redefine.cc for more
+    // method and it->second should be identical. (See openjdkjvmti/ti_redefine.cc for more
     // information.)
     DCHECK_EQ(it->second->GetNonObsoleteMethod(), method->GetNonObsoleteMethod())
         << ArtMethod::PrettyMethod(method->GetNonObsoleteMethod()) << " "
diff --git a/runtime/mirror/accessible_object.h b/runtime/mirror/accessible_object.h
index a217193..d489f14 100644
--- a/runtime/mirror/accessible_object.h
+++ b/runtime/mirror/accessible_object.h
@@ -17,11 +17,8 @@
 #ifndef ART_RUNTIME_MIRROR_ACCESSIBLE_OBJECT_H_
 #define ART_RUNTIME_MIRROR_ACCESSIBLE_OBJECT_H_
 
-#include "class.h"
-#include "gc_root.h"
 #include "object.h"
 #include "read_barrier_option.h"
-#include "thread.h"
 
 namespace art {
 
@@ -34,12 +31,6 @@
     return OFFSET_OF_OBJECT_MEMBER(AccessibleObject, flag_);
   }
 
-  template<bool kTransactionActive>
-  void SetAccessible(bool value) REQUIRES_SHARED(Locks::mutator_lock_) {
-    UNUSED(padding_);
-    return SetFieldBoolean<kTransactionActive>(FlagOffset(), value ? 1u : 0u);
-  }
-
   bool IsAccessible() REQUIRES_SHARED(Locks::mutator_lock_) {
     return GetFieldBoolean(FlagOffset());
   }
@@ -47,7 +38,7 @@
  private:
   uint8_t flag_;
   // Padding required for correct alignment of subclasses like Executable, Field, etc.
-  uint8_t padding_[1];
+  uint8_t padding_[1] ATTRIBUTE_UNUSED;
 
   DISALLOW_IMPLICIT_CONSTRUCTORS(AccessibleObject);
 };
diff --git a/runtime/mirror/array-inl.h b/runtime/mirror/array-inl.h
index 63142d5..2281245 100644
--- a/runtime/mirror/array-inl.h
+++ b/runtime/mirror/array-inl.h
@@ -27,8 +27,7 @@
 #include "class.h"
 #include "gc/heap-inl.h"
 #include "obj_ptr-inl.h"
-#include "object-inl.h"
-#include "thread.h"
+#include "thread-current-inl.h"
 
 namespace art {
 namespace mirror {
diff --git a/runtime/mirror/class.cc b/runtime/mirror/class.cc
index 6642869..c775cf4 100644
--- a/runtime/mirror/class.cc
+++ b/runtime/mirror/class.cc
@@ -464,14 +464,25 @@
   return FindInterfaceMethod(name, signature, pointer_size);
 }
 
+static inline bool IsValidInheritanceCheck(ObjPtr<mirror::Class> klass,
+                                           ObjPtr<mirror::Class> declaring_class)
+    REQUIRES_SHARED(Locks::mutator_lock_) {
+  if (klass->IsArrayClass()) {
+    return declaring_class->IsObjectClass();
+  } else if (klass->IsInterface()) {
+    return declaring_class->IsObjectClass() || declaring_class == klass;
+  } else {
+    return klass->IsSubClass(declaring_class);
+  }
+}
+
 static inline bool IsInheritedMethod(ObjPtr<mirror::Class> klass,
                                      ObjPtr<mirror::Class> declaring_class,
                                      ArtMethod& method)
     REQUIRES_SHARED(Locks::mutator_lock_) {
   DCHECK_EQ(declaring_class, method.GetDeclaringClass());
   DCHECK_NE(klass, declaring_class);
-  DCHECK(klass->IsArrayClass() ? declaring_class->IsObjectClass()
-                               : klass->IsSubClass(declaring_class));
+  DCHECK(IsValidInheritanceCheck(klass, declaring_class));
   uint32_t access_flags = method.GetAccessFlags();
   if ((access_flags & (kAccPublic | kAccProtected)) != 0) {
     return true;
diff --git a/runtime/mirror/field-inl.h b/runtime/mirror/field-inl.h
index d33df5c..ad48202 100644
--- a/runtime/mirror/field-inl.h
+++ b/runtime/mirror/field-inl.h
@@ -20,7 +20,8 @@
 #include "field.h"
 
 #include "art_field-inl.h"
-#include "mirror/dex_cache-inl.h"
+#include "class-inl.h"
+#include "dex_cache-inl.h"
 
 namespace art {
 
@@ -87,6 +88,10 @@
   SetFieldObject<kTransactionActive>(OFFSET_OF_OBJECT_MEMBER(Field, type_), type);
 }
 
+inline Primitive::Type Field::GetTypeAsPrimitiveType() {
+  return GetType()->GetPrimitiveType();
+}
+
 }  // namespace mirror
 }  // namespace art
 
diff --git a/runtime/mirror/field.h b/runtime/mirror/field.h
index 40186a6..6845575 100644
--- a/runtime/mirror/field.h
+++ b/runtime/mirror/field.h
@@ -20,8 +20,10 @@
 #include "accessible_object.h"
 #include "base/enums.h"
 #include "gc_root.h"
+#include "modifiers.h"
 #include "obj_ptr.h"
 #include "object.h"
+#include "primitive.h"
 #include "read_barrier_option.h"
 
 namespace art {
@@ -69,10 +71,7 @@
     return (GetAccessFlags() & kAccVolatile) != 0;
   }
 
-  ALWAYS_INLINE Primitive::Type GetTypeAsPrimitiveType()
-      REQUIRES_SHARED(Locks::mutator_lock_) {
-    return GetType()->GetPrimitiveType();
-  }
+  ALWAYS_INLINE Primitive::Type GetTypeAsPrimitiveType() REQUIRES_SHARED(Locks::mutator_lock_);
 
   mirror::Class* GetType() REQUIRES_SHARED(Locks::mutator_lock_) {
     return GetFieldObject<mirror::Class>(OFFSET_OF_OBJECT_MEMBER(Field, type_));
diff --git a/runtime/mirror/object-inl.h b/runtime/mirror/object-inl.h
index 086925b..4f8952d 100644
--- a/runtime/mirror/object-inl.h
+++ b/runtime/mirror/object-inl.h
@@ -393,14 +393,6 @@
 }
 
 template<VerifyObjectFlags kVerifyFlags, bool kIsVolatile>
-inline uint8_t Object::GetFieldBoolean(MemberOffset field_offset) {
-  if (kVerifyFlags & kVerifyThis) {
-    VerifyObject(this);
-  }
-  return GetField<uint8_t, kIsVolatile>(field_offset);
-}
-
-template<VerifyObjectFlags kVerifyFlags, bool kIsVolatile>
 inline int8_t Object::GetFieldByte(MemberOffset field_offset) {
   if (kVerifyFlags & kVerifyThis) {
     VerifyObject(this);
diff --git a/runtime/mirror/object.h b/runtime/mirror/object.h
index 886780f..aedcd66 100644
--- a/runtime/mirror/object.h
+++ b/runtime/mirror/object.h
@@ -380,7 +380,12 @@
 
   template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags, bool kIsVolatile = false>
   ALWAYS_INLINE uint8_t GetFieldBoolean(MemberOffset field_offset)
-      REQUIRES_SHARED(Locks::mutator_lock_);
+      REQUIRES_SHARED(Locks::mutator_lock_) {
+    if (kVerifyFlags & kVerifyThis) {
+      VerifyObject(this);
+    }
+    return GetField<uint8_t, kIsVolatile>(field_offset);
+  }
 
   template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags, bool kIsVolatile = false>
   ALWAYS_INLINE int8_t GetFieldByte(MemberOffset field_offset)
diff --git a/runtime/native/java_lang_reflect_Executable.cc b/runtime/native/java_lang_reflect_Executable.cc
index 2aad12d..f209f1d 100644
--- a/runtime/native/java_lang_reflect_Executable.cc
+++ b/runtime/native/java_lang_reflect_Executable.cc
@@ -260,7 +260,7 @@
   ScopedFastNativeObjectAccess soa(env);
   ArtMethod* method = ArtMethod::FromReflectedMethod(soa, javaMethod);
   method = method->GetInterfaceMethodIfProxy(kRuntimePointerSize);
-  ObjPtr<mirror::Class> return_type(method->GetReturnType(true /* resolve */));
+  ObjPtr<mirror::Class> return_type(method->ResolveReturnType());
   if (return_type.IsNull()) {
     CHECK(soa.Self()->IsExceptionPending());
     return nullptr;
diff --git a/runtime/native/java_lang_reflect_Field.cc b/runtime/native/java_lang_reflect_Field.cc
index 9f59a1f..2e4dd8a 100644
--- a/runtime/native/java_lang_reflect_Field.cc
+++ b/runtime/native/java_lang_reflect_Field.cc
@@ -27,7 +27,7 @@
 #include "dex_file_annotations.h"
 #include "jni_internal.h"
 #include "mirror/class-inl.h"
-#include "mirror/field.h"
+#include "mirror/field-inl.h"
 #include "native_util.h"
 #include "reflection-inl.h"
 #include "scoped_fast_native_object_access-inl.h"
diff --git a/runtime/oat_file_assistant.cc b/runtime/oat_file_assistant.cc
index dae41c1..3794f51 100644
--- a/runtime/oat_file_assistant.cc
+++ b/runtime/oat_file_assistant.cc
@@ -37,6 +37,7 @@
 #include "scoped_thread_state_change-inl.h"
 #include "utils.h"
 #include "vdex_file.h"
+#include "class_loader_context.h"
 
 namespace art {
 
@@ -225,13 +226,25 @@
 }
 
 OatFileAssistant::ResultOfAttemptToUpdate
-OatFileAssistant::MakeUpToDate(bool profile_changed, std::string* error_msg) {
+OatFileAssistant::MakeUpToDate(bool profile_changed,
+                               const std::string& class_loader_context,
+                               std::string* error_msg) {
   CompilerFilter::Filter target;
   if (!GetRuntimeCompilerFilterOption(&target, error_msg)) {
     return kUpdateNotAttempted;
   }
 
   OatFileInfo& info = GetBestInfo();
+  // TODO(calin, jeffhao): the context should really be passed to GetDexOptNeeded: b/62269291.
+  // This is actually not trivial in the current logic as it will interact with the collision
+  // check:
+  //   - currently, if the context does not match but we have no collisions we still accept the
+  //     oat file.
+  //   - if GetDexOptNeeded would return kDex2OatFromScratch for a context mismatch and we make
+  //     the oat code up to date the collision check becomes useless.
+  //   - however, MakeUpToDate will not always succeed (e.g. for primary apks, or for dex files
+  //     loaded in other processes). So it boils down to how far do we want to complicate
+  //     the logic in order to enable the use of oat files. Maybe its time to try simplify it.
   switch (info.GetDexOptNeeded(target, profile_changed, /*downgrade*/ false)) {
     case kNoDexOptNeeded:
       return kUpdateSucceeded;
@@ -243,7 +256,7 @@
     case kDex2OatForBootImage:
     case kDex2OatForRelocation:
     case kDex2OatForFilter:
-      return GenerateOatFileNoChecks(info, target, error_msg);
+      return GenerateOatFileNoChecks(info, target, class_loader_context, error_msg);
   }
   UNREACHABLE();
 }
@@ -628,7 +641,10 @@
 }
 
 OatFileAssistant::ResultOfAttemptToUpdate OatFileAssistant::GenerateOatFileNoChecks(
-      OatFileAssistant::OatFileInfo& info, CompilerFilter::Filter filter, std::string* error_msg) {
+      OatFileAssistant::OatFileInfo& info,
+      CompilerFilter::Filter filter,
+      const std::string& class_loader_context,
+      std::string* error_msg) {
   CHECK(error_msg != nullptr);
 
   Runtime* runtime = Runtime::Current();
@@ -704,6 +720,7 @@
   args.push_back("--oat-fd=" + std::to_string(oat_file->Fd()));
   args.push_back("--oat-location=" + oat_file_name);
   args.push_back("--compiler-filter=" + CompilerFilter::NameOfFilter(filter));
+  args.push_back("--class-loader-context=" + class_loader_context);
 
   if (!Dex2Oat(args, error_msg)) {
     // Manually delete the oat and vdex files. This ensures there is no garbage
@@ -743,14 +760,6 @@
 
   std::vector<std::string> argv;
   argv.push_back(runtime->GetCompilerExecutable());
-  argv.push_back("--runtime-arg");
-  argv.push_back("-classpath");
-  argv.push_back("--runtime-arg");
-  std::string class_path = runtime->GetClassPathString();
-  if (class_path == "") {
-    class_path = OatFile::kSpecialSharedLibrary;
-  }
-  argv.push_back(class_path);
   if (runtime->IsJavaDebuggable()) {
     argv.push_back("--debuggable");
   }
diff --git a/runtime/oat_file_assistant.h b/runtime/oat_file_assistant.h
index 320aa4f..5eec943 100644
--- a/runtime/oat_file_assistant.h
+++ b/runtime/oat_file_assistant.h
@@ -185,12 +185,17 @@
   // profile_changed should be true to indicate the profile has recently
   // changed for this dex location.
   //
+  // If the dex files need to be made up to date, class_loader_context will be
+  // passed to dex2oat.
+  //
   // Returns the result of attempting to update the code.
   //
   // If the result is not kUpdateSucceeded, the value of error_msg will be set
   // to a string describing why there was a failure or the update was not
   // attempted. error_msg must not be null.
-  ResultOfAttemptToUpdate MakeUpToDate(bool profile_changed, std::string* error_msg);
+  ResultOfAttemptToUpdate MakeUpToDate(bool profile_changed,
+                                       const std::string& class_loader_context,
+                                       std::string* error_msg);
 
   // Returns an oat file that can be used for loading dex files.
   // Returns null if no suitable oat file was found.
@@ -389,7 +394,8 @@
   };
 
   // Generate the oat file for the given info from the dex file using the
-  // current runtime compiler options and the specified filter.
+  // current runtime compiler options, the specified filter and class loader
+  // context.
   // This does not check the current status before attempting to generate the
   // oat file.
   //
@@ -398,6 +404,7 @@
   // attempted. error_msg must not be null.
   ResultOfAttemptToUpdate GenerateOatFileNoChecks(OatFileInfo& info,
                                                   CompilerFilter::Filter target,
+                                                  const std::string& class_loader_context,
                                                   std::string* error_msg);
 
   // Return info for the best oat file.
diff --git a/runtime/oat_file_assistant_test.cc b/runtime/oat_file_assistant_test.cc
index c59dafc..e048177 100644
--- a/runtime/oat_file_assistant_test.cc
+++ b/runtime/oat_file_assistant_test.cc
@@ -27,8 +27,10 @@
 
 #include "art_field-inl.h"
 #include "class_linker-inl.h"
+#include "class_loader_context.h"
 #include "common_runtime_test.h"
 #include "dexopt_test.h"
+#include "oat_file.h"
 #include "oat_file_manager.h"
 #include "os.h"
 #include "scoped_thread_state_change-inl.h"
@@ -37,6 +39,8 @@
 
 namespace art {
 
+static const std::string kSpecialSharedLibrary = "&";
+
 class OatFileAssistantTest : public DexoptTest {};
 
 class OatFileAssistantNoDex2OatTest : public DexoptTest {
@@ -116,7 +120,8 @@
 
   // Trying to make the oat file up to date should not fail or crash.
   std::string error_msg;
-  EXPECT_EQ(OatFileAssistant::kUpdateSucceeded, oat_file_assistant.MakeUpToDate(false, &error_msg));
+  EXPECT_EQ(OatFileAssistant::kUpdateSucceeded,
+          oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibrary, &error_msg));
 
   // Trying to get the best oat file should fail, but not crash.
   std::unique_ptr<OatFile> oat_file = oat_file_assistant.GetBestOatFile();
@@ -769,7 +774,8 @@
   std::string error_msg;
   Runtime::Current()->AddCompilerOption("--compiler-filter=speed");
   EXPECT_EQ(OatFileAssistant::kUpdateSucceeded,
-      oat_file_assistant.MakeUpToDate(false, &error_msg)) << error_msg;
+      oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibrary, &error_msg)) <<
+          error_msg;
 
   EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded,
       oat_file_assistant.GetDexOptNeeded(CompilerFilter::kSpeed));
@@ -949,7 +955,7 @@
   // We should get kUpdateSucceeded from MakeUpToDate since there's nothing
   // that can be done in this situation.
   ASSERT_EQ(OatFileAssistant::kUpdateSucceeded,
-      oat_file_assistant.MakeUpToDate(false, &error_msg));
+      oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibrary, &error_msg));
 
   // Verify it didn't create an oat in the default location (dalvik-cache).
   OatFileAssistant ofm(dex_location.c_str(), kRuntimeISA, false);
@@ -1028,7 +1034,7 @@
   std::string error_msg;
   Runtime::Current()->AddCompilerOption("--compiler-filter=speed");
   EXPECT_EQ(OatFileAssistant::kUpdateSucceeded,
-      oat_file_assistant.MakeUpToDate(false, &error_msg));
+      oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibrary, &error_msg));
   EXPECT_TRUE(error_msg.empty());
 }
 
@@ -1175,7 +1181,8 @@
   std::string error_msg;
   Runtime::Current()->AddCompilerOption("--compiler-filter=quicken");
   EXPECT_EQ(OatFileAssistant::kUpdateSucceeded,
-      oat_file_assistant.MakeUpToDate(false, &error_msg)) << error_msg;
+      oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibrary, &error_msg)) <<
+          error_msg;
   EXPECT_EQ(-OatFileAssistant::kNoDexOptNeeded,
       oat_file_assistant.GetDexOptNeeded(CompilerFilter::kQuicken));
   EXPECT_EQ(-OatFileAssistant::kDex2OatForFilter,
@@ -1183,7 +1190,8 @@
 
   Runtime::Current()->AddCompilerOption("--compiler-filter=speed");
   EXPECT_EQ(OatFileAssistant::kUpdateSucceeded,
-      oat_file_assistant.MakeUpToDate(false, &error_msg)) << error_msg;
+      oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibrary, &error_msg))
+          << error_msg;
   EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded,
       oat_file_assistant.GetDexOptNeeded(CompilerFilter::kQuicken));
   EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded,
@@ -1191,7 +1199,7 @@
 
   Runtime::Current()->AddCompilerOption("--compiler-filter=bogus");
   EXPECT_EQ(OatFileAssistant::kUpdateNotAttempted,
-      oat_file_assistant.MakeUpToDate(false, &error_msg));
+      oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibrary, &error_msg));
 }
 
 TEST(OatFileAssistantUtilsTest, DexLocationToOdexFilename) {
@@ -1251,7 +1259,8 @@
       OatFileAssistant::kDefaultCompilerFilterForDexLoading;
   std::string error_msg;
   EXPECT_EQ(OatFileAssistant::kUpdateSucceeded,
-      oat_file_assistant.MakeUpToDate(false, &error_msg)) << error_msg;
+      oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibrary, &error_msg)) <<
+          error_msg;
   EXPECT_EQ(-OatFileAssistant::kNoDexOptNeeded,
             oat_file_assistant.GetDexOptNeeded(default_filter));
   std::unique_ptr<OatFile> oat_file = oat_file_assistant.GetBestOatFile();
@@ -1259,6 +1268,50 @@
   EXPECT_EQ(default_filter, oat_file->GetCompilerFilter());
 }
 
+TEST_F(OatFileAssistantTest, MakeUpToDateWithSpecialSharedLibrary) {
+  std::string dex_location = GetScratchDir() + "/TestDex.jar";
+  Copy(GetDexSrc1(), dex_location);
+
+  OatFileAssistant oat_file_assistant(dex_location.c_str(), kRuntimeISA, false);
+
+  const CompilerFilter::Filter default_filter =
+      OatFileAssistant::kDefaultCompilerFilterForDexLoading;
+  std::string error_msg;
+  int status = oat_file_assistant.MakeUpToDate(false, kSpecialSharedLibrary, &error_msg);
+  EXPECT_EQ(OatFileAssistant::kUpdateSucceeded, status) << error_msg;
+  EXPECT_EQ(-OatFileAssistant::kNoDexOptNeeded,
+      oat_file_assistant.GetDexOptNeeded(default_filter));
+  std::unique_ptr<OatFile> oat_file = oat_file_assistant.GetBestOatFile();
+  EXPECT_NE(nullptr, oat_file.get());
+  EXPECT_EQ(kSpecialSharedLibrary,
+            oat_file->GetOatHeader().GetStoreValueByKey(OatHeader::kClassPathKey));
+}
+
+TEST_F(OatFileAssistantTest, MakeUpToDateWithContext) {
+  std::string dex_location = GetScratchDir() + "/TestDex.jar";
+  std::string context_location = GetScratchDir() + "/ContextDex.jar";
+  Copy(GetDexSrc1(), dex_location);
+  Copy(GetDexSrc2(), context_location);
+
+  OatFileAssistant oat_file_assistant(dex_location.c_str(), kRuntimeISA, false);
+
+  const CompilerFilter::Filter default_filter =
+      OatFileAssistant::kDefaultCompilerFilterForDexLoading;
+  std::string error_msg;
+  std::string context_str = "PCL[" + context_location + "]";
+  int status = oat_file_assistant.MakeUpToDate(false, context_str, &error_msg);
+  EXPECT_EQ(OatFileAssistant::kUpdateSucceeded, status) << error_msg;
+  EXPECT_EQ(-OatFileAssistant::kNoDexOptNeeded,
+  oat_file_assistant.GetDexOptNeeded(default_filter));
+  std::unique_ptr<OatFile> oat_file = oat_file_assistant.GetBestOatFile();
+  EXPECT_NE(nullptr, oat_file.get());
+  std::unique_ptr<ClassLoaderContext> context =
+      ClassLoaderContext::Create(context_str);
+  context->OpenDexFiles(kRuntimeISA, "");
+  EXPECT_EQ(context->EncodeContextForOatFile(""),
+      oat_file->GetOatHeader().GetStoreValueByKey(OatHeader::kClassPathKey));
+}
+
 // TODO: More Tests:
 //  * Test class linker falls back to unquickened dex for DexNoOat
 //  * Test class linker falls back to unquickened dex for MultiDexNoOat
diff --git a/runtime/oat_file_manager.cc b/runtime/oat_file_manager.cc
index e950fca..5baf59c 100644
--- a/runtime/oat_file_manager.cc
+++ b/runtime/oat_file_manager.cc
@@ -329,12 +329,14 @@
 
 // Check for class-def collisions in dex files.
 //
-// This first walks the class loader chain, getting all the dex files from the class loader. If
-// the class loader is null or one of the class loaders in the chain is unsupported, we collect
-// dex files from all open non-boot oat files to be safe.
+// This first walks the class loader chain present in the given context, getting all the dex files
+// from the class loader.
 //
-// This first checks whether the shared libraries are in the expected order and the oat files
-// have the expected checksums. If so, we exit early. Otherwise, we do the collision check.
+// If the context is null (which means the initial class loader was null or unsupported)
+// this returns false. b/37777332.
+//
+// This first checks whether all class loaders in the context have the same type and
+// classpath. If so, we exit early. Otherwise, we do the collision check.
 //
 // The collision check works by maintaining a heap with one class from each dex file, sorted by the
 // class descriptor. Then a dex-file/class pair is continually removed from the heap and compared
@@ -342,23 +344,11 @@
 // the two elements agree on whether their dex file was from an already-loaded oat-file or the
 // new oat file. Any disagreement indicates a collision.
 bool OatFileManager::HasCollisions(const OatFile* oat_file,
-                                   jobject class_loader,
-                                   jobjectArray dex_elements,
+                                   const ClassLoaderContext* context,
                                    std::string* error_msg /*out*/) const {
   DCHECK(oat_file != nullptr);
   DCHECK(error_msg != nullptr);
 
-  // If the class_loader is null there's not much we can do. This happens if a dex files is loaded
-  // directly with DexFile APIs instead of using class loaders.
-  if (class_loader == nullptr) {
-    LOG(WARNING) << "Opening an oat file without a class loader. "
-        << "Are you using the deprecated DexFile APIs?";
-    return false;
-  }
-
-  std::unique_ptr<ClassLoaderContext> context =
-      ClassLoaderContext::CreateContextForClassLoader(class_loader, dex_elements);
-
   // The context might be null if there are unrecognized class loaders in the chain or they
   // don't meet sensible sanity conditions. In this case we assume that the app knows what it's
   // doing and accept the oat file.
@@ -406,6 +396,17 @@
   Locks::mutator_lock_->AssertNotHeld(self);
   Runtime* const runtime = Runtime::Current();
 
+  std::unique_ptr<ClassLoaderContext> context;
+  // If the class_loader is null there's not much we can do. This happens if a dex files is loaded
+  // directly with DexFile APIs instead of using class loaders.
+  if (class_loader == nullptr) {
+    LOG(WARNING) << "Opening an oat file without a class loader. "
+                 << "Are you using the deprecated DexFile APIs?";
+    context = nullptr;
+  } else {
+    context = ClassLoaderContext::CreateContextForClassLoader(class_loader, dex_elements);
+  }
+
   OatFileAssistant oat_file_assistant(dex_location,
                                       kRuntimeISA,
                                       !runtime->IsAotCompiler());
@@ -425,7 +426,12 @@
     // Update the oat file on disk if we can, based on the --compiler-filter
     // option derived from the current runtime options.
     // This may fail, but that's okay. Best effort is all that matters here.
-    switch (oat_file_assistant.MakeUpToDate(/*profile_changed*/false, /*out*/ &error_msg)) {
+
+    const std::string& dex2oat_context = context == nullptr
+        ? OatFile::kSpecialSharedLibrary
+        : context->EncodeContextForDex2oat(/*base_dir*/ "");
+    switch (oat_file_assistant.MakeUpToDate(
+        /*profile_changed*/false, dex2oat_context, /*out*/ &error_msg)) {
       case OatFileAssistant::kUpdateFailed:
         LOG(WARNING) << error_msg;
         break;
@@ -451,7 +457,7 @@
   if ((class_loader != nullptr || dex_elements != nullptr) && oat_file != nullptr) {
     // Take the file only if it has no collisions, or we must take it because of preopting.
     bool accept_oat_file =
-        !HasCollisions(oat_file.get(), class_loader, dex_elements, /*out*/ &error_msg);
+        !HasCollisions(oat_file.get(), context.get(), /*out*/ &error_msg);
     if (!accept_oat_file) {
       // Failed the collision check. Print warning.
       if (Runtime::Current()->IsDexFileFallbackEnabled()) {
diff --git a/runtime/oat_file_manager.h b/runtime/oat_file_manager.h
index 05a5f5b..4523494 100644
--- a/runtime/oat_file_manager.h
+++ b/runtime/oat_file_manager.h
@@ -35,6 +35,7 @@
 }  // namespace space
 }  // namespace gc
 
+class ClassLoaderContext;
 class DexFile;
 class OatFile;
 
@@ -105,15 +106,17 @@
   void DumpForSigQuit(std::ostream& os);
 
  private:
-  // Check that the shared libraries in the given oat file match those in the given class loader and
-  // dex elements. If the class loader is null or we do not support one of the class loaders in the
-  // chain, compare against all non-boot oat files instead. If the shared libraries are not ok,
-  // check for duplicate class definitions of the given oat file against the oat files (either from
-  // the class loader and dex elements if possible or all non-boot oat files otherwise).
+  // Check that the class loader context of the given oat file matches the given context.
+  // This will perform a check that all class loaders in the chain have the same type and
+  // classpath.
+  // If the context is null (which means the initial class loader was null or unsupported)
+  // this returns false.
+  // If the context does not validate the method will check for duplicate class definitions of
+  // the given oat file against the oat files (either from the class loaders if possible or all
+  // non-boot oat files otherwise).
   // Return true if there are any class definition collisions in the oat_file.
   bool HasCollisions(const OatFile* oat_file,
-                     jobject class_loader,
-                     jobjectArray dex_elements,
+                     const ClassLoaderContext* context,
                      /*out*/ std::string* error_msg) const
       REQUIRES(!Locks::oat_file_manager_lock_);
 
diff --git a/runtime/openjdkjvmti/ti_method.cc b/runtime/openjdkjvmti/ti_method.cc
deleted file mode 100644
index ab434d7..0000000
--- a/runtime/openjdkjvmti/ti_method.cc
+++ /dev/null
@@ -1,428 +0,0 @@
-/* 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_method.h"
-
-#include "art_jvmti.h"
-#include "art_method-inl.h"
-#include "base/enums.h"
-#include "dex_file_annotations.h"
-#include "events-inl.h"
-#include "jni_internal.h"
-#include "mirror/object_array-inl.h"
-#include "modifiers.h"
-#include "nativehelper/ScopedLocalRef.h"
-#include "runtime_callbacks.h"
-#include "scoped_thread_state_change-inl.h"
-#include "thread-current-inl.h"
-#include "thread_list.h"
-#include "ti_phase.h"
-
-namespace openjdkjvmti {
-
-struct TiMethodCallback : public art::MethodCallback {
-  void RegisterNativeMethod(art::ArtMethod* method,
-                            const void* cur_method,
-                            /*out*/void** new_method)
-      OVERRIDE REQUIRES_SHARED(art::Locks::mutator_lock_) {
-    if (event_handler->IsEventEnabledAnywhere(ArtJvmtiEvent::kNativeMethodBind)) {
-      art::Thread* thread = art::Thread::Current();
-      art::JNIEnvExt* jnienv = thread->GetJniEnv();
-      ScopedLocalRef<jthread> thread_jni(
-          jnienv, PhaseUtil::IsLivePhase() ? jnienv->AddLocalReference<jthread>(thread->GetPeer())
-                                           : nullptr);
-      art::ScopedThreadSuspension sts(thread, art::ThreadState::kNative);
-      event_handler->DispatchEvent<ArtJvmtiEvent::kNativeMethodBind>(
-          thread,
-          static_cast<JNIEnv*>(jnienv),
-          thread_jni.get(),
-          art::jni::EncodeArtMethod(method),
-          const_cast<void*>(cur_method),
-          new_method);
-    }
-  }
-
-  EventHandler* event_handler = nullptr;
-};
-
-TiMethodCallback gMethodCallback;
-
-void MethodUtil::Register(EventHandler* handler) {
-  gMethodCallback.event_handler = handler;
-  art::ScopedThreadStateChange stsc(art::Thread::Current(),
-                                    art::ThreadState::kWaitingForDebuggerToAttach);
-  art::ScopedSuspendAll ssa("Add method callback");
-  art::Runtime::Current()->GetRuntimeCallbacks()->AddMethodCallback(&gMethodCallback);
-}
-
-void MethodUtil::Unregister() {
-  art::ScopedThreadStateChange stsc(art::Thread::Current(),
-                                    art::ThreadState::kWaitingForDebuggerToAttach);
-  art::ScopedSuspendAll ssa("Remove method callback");
-  art::Runtime* runtime = art::Runtime::Current();
-  runtime->GetRuntimeCallbacks()->RemoveMethodCallback(&gMethodCallback);
-}
-
-jvmtiError MethodUtil::GetBytecodes(jvmtiEnv* env,
-                                    jmethodID method,
-                                    jint* size_ptr,
-                                    unsigned char** bytecode_ptr) {
-  if (method == nullptr) {
-    return ERR(INVALID_METHODID);
-  }
-  art::ArtMethod* art_method = art::jni::DecodeArtMethod(method);
-
-  if (art_method->IsNative()) {
-    return ERR(NATIVE_METHOD);
-  }
-
-  if (size_ptr == nullptr || bytecode_ptr == nullptr) {
-    return ERR(NULL_POINTER);
-  }
-
-  art::ScopedObjectAccess soa(art::Thread::Current());
-  const art::DexFile::CodeItem* code_item = art_method->GetCodeItem();
-  if (code_item == nullptr) {
-    *size_ptr = 0;
-    *bytecode_ptr = nullptr;
-    return OK;
-  }
-  // 2 bytes per instruction for dex code.
-  *size_ptr = code_item->insns_size_in_code_units_ * 2;
-  jvmtiError err = env->Allocate(*size_ptr, bytecode_ptr);
-  if (err != OK) {
-    return err;
-  }
-  memcpy(*bytecode_ptr, code_item->insns_, *size_ptr);
-  return OK;
-}
-
-jvmtiError MethodUtil::GetArgumentsSize(jvmtiEnv* env ATTRIBUTE_UNUSED,
-                                        jmethodID method,
-                                        jint* size_ptr) {
-  if (method == nullptr) {
-    return ERR(INVALID_METHODID);
-  }
-  art::ArtMethod* art_method = art::jni::DecodeArtMethod(method);
-
-  if (art_method->IsNative()) {
-    return ERR(NATIVE_METHOD);
-  }
-
-  if (size_ptr == nullptr) {
-    return ERR(NULL_POINTER);
-  }
-
-  art::ScopedObjectAccess soa(art::Thread::Current());
-  if (art_method->IsProxyMethod() || art_method->IsAbstract()) {
-    // Use the shorty.
-    art::ArtMethod* base_method = art_method->GetInterfaceMethodIfProxy(art::kRuntimePointerSize);
-    size_t arg_count = art::ArtMethod::NumArgRegisters(base_method->GetShorty());
-    if (!base_method->IsStatic()) {
-      arg_count++;
-    }
-    *size_ptr = static_cast<jint>(arg_count);
-    return ERR(NONE);
-  }
-
-  DCHECK_NE(art_method->GetCodeItemOffset(), 0u);
-  *size_ptr = art_method->GetCodeItem()->ins_size_;
-
-  return ERR(NONE);
-}
-
-jvmtiError MethodUtil::GetMaxLocals(jvmtiEnv* env ATTRIBUTE_UNUSED,
-                                    jmethodID method,
-                                    jint* max_ptr) {
-  if (method == nullptr) {
-    return ERR(INVALID_METHODID);
-  }
-  art::ArtMethod* art_method = art::jni::DecodeArtMethod(method);
-
-  if (art_method->IsNative()) {
-    return ERR(NATIVE_METHOD);
-  }
-
-  if (max_ptr == nullptr) {
-    return ERR(NULL_POINTER);
-  }
-
-  art::ScopedObjectAccess soa(art::Thread::Current());
-  if (art_method->IsProxyMethod() || art_method->IsAbstract()) {
-    // This isn't specified as an error case, so return 0.
-    *max_ptr = 0;
-    return ERR(NONE);
-  }
-
-  DCHECK_NE(art_method->GetCodeItemOffset(), 0u);
-  *max_ptr = art_method->GetCodeItem()->registers_size_;
-
-  return ERR(NONE);
-}
-
-jvmtiError MethodUtil::GetMethodName(jvmtiEnv* env,
-                                     jmethodID method,
-                                     char** name_ptr,
-                                     char** signature_ptr,
-                                     char** generic_ptr) {
-  art::ScopedObjectAccess soa(art::Thread::Current());
-  art::ArtMethod* art_method = art::jni::DecodeArtMethod(method);
-  art_method = art_method->GetInterfaceMethodIfProxy(art::kRuntimePointerSize);
-
-  JvmtiUniquePtr<char[]> name_copy;
-  if (name_ptr != nullptr) {
-    const char* method_name = art_method->GetName();
-    if (method_name == nullptr) {
-      method_name = "<error>";
-    }
-    jvmtiError ret;
-    name_copy = CopyString(env, method_name, &ret);
-    if (name_copy == nullptr) {
-      return ret;
-    }
-    *name_ptr = name_copy.get();
-  }
-
-  JvmtiUniquePtr<char[]> signature_copy;
-  if (signature_ptr != nullptr) {
-    const art::Signature sig = art_method->GetSignature();
-    std::string str = sig.ToString();
-    jvmtiError ret;
-    signature_copy = CopyString(env, str.c_str(), &ret);
-    if (signature_copy == nullptr) {
-      return ret;
-    }
-    *signature_ptr = signature_copy.get();
-  }
-
-  if (generic_ptr != nullptr) {
-    *generic_ptr = nullptr;
-    if (!art_method->GetDeclaringClass()->IsProxyClass()) {
-      art::mirror::ObjectArray<art::mirror::String>* str_array =
-          art::annotations::GetSignatureAnnotationForMethod(art_method);
-      if (str_array != nullptr) {
-        std::ostringstream oss;
-        for (int32_t i = 0; i != str_array->GetLength(); ++i) {
-          oss << str_array->Get(i)->ToModifiedUtf8();
-        }
-        std::string output_string = oss.str();
-        jvmtiError ret;
-        JvmtiUniquePtr<char[]> generic_copy = CopyString(env, output_string.c_str(), &ret);
-        if (generic_copy == nullptr) {
-          return ret;
-        }
-        *generic_ptr = generic_copy.release();
-      } else if (soa.Self()->IsExceptionPending()) {
-        // TODO: Should we report an error here?
-        soa.Self()->ClearException();
-      }
-    }
-  }
-
-  // Everything is fine, release the buffers.
-  name_copy.release();
-  signature_copy.release();
-
-  return ERR(NONE);
-}
-
-jvmtiError MethodUtil::GetMethodDeclaringClass(jvmtiEnv* env ATTRIBUTE_UNUSED,
-                                               jmethodID method,
-                                               jclass* declaring_class_ptr) {
-  if (declaring_class_ptr == nullptr) {
-    return ERR(NULL_POINTER);
-  }
-
-  art::ArtMethod* art_method = art::jni::DecodeArtMethod(method);
-  // Note: No GetInterfaceMethodIfProxy, we want to actual class.
-
-  art::ScopedObjectAccess soa(art::Thread::Current());
-  art::mirror::Class* klass = art_method->GetDeclaringClass();
-  *declaring_class_ptr = soa.AddLocalReference<jclass>(klass);
-
-  return ERR(NONE);
-}
-
-jvmtiError MethodUtil::GetMethodLocation(jvmtiEnv* env ATTRIBUTE_UNUSED,
-                                         jmethodID method,
-                                         jlocation* start_location_ptr,
-                                         jlocation* end_location_ptr) {
-  if (method == nullptr) {
-    return ERR(INVALID_METHODID);
-  }
-  art::ArtMethod* art_method = art::jni::DecodeArtMethod(method);
-
-  if (art_method->IsNative()) {
-    return ERR(NATIVE_METHOD);
-  }
-
-  if (start_location_ptr == nullptr || end_location_ptr == nullptr) {
-    return ERR(NULL_POINTER);
-  }
-
-  art::ScopedObjectAccess soa(art::Thread::Current());
-  if (art_method->IsProxyMethod() || art_method->IsAbstract()) {
-    // This isn't specified as an error case, so return -1/-1 as the RI does.
-    *start_location_ptr = -1;
-    *end_location_ptr = -1;
-    return ERR(NONE);
-  }
-
-  DCHECK_NE(art_method->GetCodeItemOffset(), 0u);
-  *start_location_ptr = 0;
-  *end_location_ptr = art_method->GetCodeItem()->insns_size_in_code_units_ - 1;
-
-  return ERR(NONE);
-}
-
-jvmtiError MethodUtil::GetMethodModifiers(jvmtiEnv* env ATTRIBUTE_UNUSED,
-                                          jmethodID method,
-                                          jint* modifiers_ptr) {
-  if (modifiers_ptr == nullptr) {
-    return ERR(NULL_POINTER);
-  }
-
-  art::ArtMethod* art_method = art::jni::DecodeArtMethod(method);
-  uint32_t modifiers = art_method->GetAccessFlags();
-
-  // Note: Keep this code in sync with Executable.fixMethodFlags.
-  if ((modifiers & art::kAccAbstract) != 0) {
-    modifiers &= ~art::kAccNative;
-  }
-  modifiers &= ~art::kAccSynchronized;
-  if ((modifiers & art::kAccDeclaredSynchronized) != 0) {
-    modifiers |= art::kAccSynchronized;
-  }
-  modifiers &= art::kAccJavaFlagsMask;
-
-  *modifiers_ptr = modifiers;
-  return ERR(NONE);
-}
-
-using LineNumberContext = std::vector<jvmtiLineNumberEntry>;
-
-static bool CollectLineNumbers(void* void_context, const art::DexFile::PositionInfo& entry) {
-  LineNumberContext* context = reinterpret_cast<LineNumberContext*>(void_context);
-  jvmtiLineNumberEntry jvmti_entry = { static_cast<jlocation>(entry.address_),
-                                       static_cast<jint>(entry.line_) };
-  context->push_back(jvmti_entry);
-  return false;  // Collect all, no early exit.
-}
-
-jvmtiError MethodUtil::GetLineNumberTable(jvmtiEnv* env,
-                                          jmethodID method,
-                                          jint* entry_count_ptr,
-                                          jvmtiLineNumberEntry** table_ptr) {
-  if (method == nullptr) {
-    return ERR(NULL_POINTER);
-  }
-  art::ArtMethod* art_method = art::jni::DecodeArtMethod(method);
-  DCHECK(!art_method->IsRuntimeMethod());
-
-  const art::DexFile::CodeItem* code_item;
-  const art::DexFile* dex_file;
-  {
-    art::ScopedObjectAccess soa(art::Thread::Current());
-
-    if (art_method->IsProxyMethod()) {
-      return ERR(ABSENT_INFORMATION);
-    }
-    if (art_method->IsNative()) {
-      return ERR(NATIVE_METHOD);
-    }
-    if (entry_count_ptr == nullptr || table_ptr == nullptr) {
-      return ERR(NULL_POINTER);
-    }
-
-    code_item = art_method->GetCodeItem();
-    dex_file = art_method->GetDexFile();
-    DCHECK(code_item != nullptr) << art_method->PrettyMethod() << " " << dex_file->GetLocation();
-  }
-
-  LineNumberContext context;
-  bool success = dex_file->DecodeDebugPositionInfo(code_item, CollectLineNumbers, &context);
-  if (!success) {
-    return ERR(ABSENT_INFORMATION);
-  }
-
-  unsigned char* data;
-  jlong mem_size = context.size() * sizeof(jvmtiLineNumberEntry);
-  jvmtiError alloc_error = env->Allocate(mem_size, &data);
-  if (alloc_error != ERR(NONE)) {
-    return alloc_error;
-  }
-  *table_ptr = reinterpret_cast<jvmtiLineNumberEntry*>(data);
-  memcpy(*table_ptr, context.data(), mem_size);
-  *entry_count_ptr = static_cast<jint>(context.size());
-
-  return ERR(NONE);
-}
-
-template <typename T>
-static jvmtiError IsMethodT(jvmtiEnv* env ATTRIBUTE_UNUSED,
-                            jmethodID method,
-                            T test,
-                            jboolean* is_t_ptr) {
-  if (method == nullptr) {
-    return ERR(INVALID_METHODID);
-  }
-  if (is_t_ptr == nullptr) {
-    return ERR(NULL_POINTER);
-  }
-
-  art::ArtMethod* art_method = art::jni::DecodeArtMethod(method);
-  *is_t_ptr = test(art_method) ? JNI_TRUE : JNI_FALSE;
-
-  return ERR(NONE);
-}
-
-jvmtiError MethodUtil::IsMethodNative(jvmtiEnv* env, jmethodID m, jboolean* is_native_ptr) {
-  auto test = [](art::ArtMethod* method) {
-    return method->IsNative();
-  };
-  return IsMethodT(env, m, test, is_native_ptr);
-}
-
-jvmtiError MethodUtil::IsMethodObsolete(jvmtiEnv* env, jmethodID m, jboolean* is_obsolete_ptr) {
-  auto test = [](art::ArtMethod* method) {
-    return method->IsObsolete();
-  };
-  return IsMethodT(env, m, test, is_obsolete_ptr);
-}
-
-jvmtiError MethodUtil::IsMethodSynthetic(jvmtiEnv* env, jmethodID m, jboolean* is_synthetic_ptr) {
-  auto test = [](art::ArtMethod* method) {
-    return method->IsSynthetic();
-  };
-  return IsMethodT(env, m, test, is_synthetic_ptr);
-}
-
-}  // namespace openjdkjvmti
diff --git a/runtime/reflection.cc b/runtime/reflection.cc
index 6f1d15c..f28f0ca 100644
--- a/runtime/reflection.cc
+++ b/runtime/reflection.cc
@@ -238,8 +238,7 @@
         // 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_,
-                                     true /* resolve */));
+            m->ResolveClassFromTypeIndex(classes->GetTypeItem(args_offset).type_idx_));
         if (dst_class.Ptr() == nullptr) {
           CHECK(self->IsExceptionPending());
           return false;
@@ -378,7 +377,7 @@
   Thread* const self = Thread::Current();
   for (uint32_t i = 0; i < num_params; i++) {
     dex::TypeIndex type_idx = params->GetTypeItem(i).type_idx_;
-    ObjPtr<mirror::Class> param_type(m->GetClassFromTypeIndex(type_idx, true /* resolve */));
+    ObjPtr<mirror::Class> param_type(m->ResolveClassFromTypeIndex(type_idx));
     if (param_type == nullptr) {
       CHECK(self->IsExceptionPending());
       LOG(ERROR) << "Internal error: unresolvable type for argument type in JNI invoke: "
diff --git a/runtime/runtime.cc b/runtime/runtime.cc
index 6fbf64b..a8ccf89 100644
--- a/runtime/runtime.cc
+++ b/runtime/runtime.cc
@@ -38,6 +38,7 @@
 
 #include "android-base/strings.h"
 
+#include "aot_class_linker.h"
 #include "arch/arm/quick_method_frame_info_arm.h"
 #include "arch/arm/registers_arm.h"
 #include "arch/arm64/quick_method_frame_info_arm64.h"
@@ -237,7 +238,7 @@
       system_thread_group_(nullptr),
       system_class_loader_(nullptr),
       dump_gc_performance_on_shutdown_(false),
-      preinitialization_transaction_(nullptr),
+      preinitialization_transactions_(),
       verify_(verifier::VerifyMode::kNone),
       allow_dex_file_fallback_(true),
       target_sdk_version_(0),
@@ -1283,7 +1284,11 @@
   GetHeap()->EnableObjectValidation();
 
   CHECK_GE(GetHeap()->GetContinuousSpaces().size(), 1U);
-  class_linker_ = new ClassLinker(intern_table_);
+  if (UNLIKELY(IsAotCompiler())) {
+    class_linker_ = new AotClassLinker(intern_table_);
+  } else {
+    class_linker_ = new ClassLinker(intern_table_);
+  }
   if (GetHeap()->HasBootImageSpace()) {
     bool result = class_linker_->InitFromBootImage(&error_msg);
     if (!result) {
@@ -1829,8 +1834,8 @@
 }
 
 void Runtime::VisitTransactionRoots(RootVisitor* visitor) {
-  if (preinitialization_transaction_ != nullptr) {
-    preinitialization_transaction_->VisitRoots(visitor);
+  for (auto& transaction : preinitialization_transactions_) {
+    transaction->VisitRoots(visitor);
   }
 }
 
@@ -2062,28 +2067,32 @@
 }
 
 // Transaction support.
+bool Runtime::IsActiveTransaction() const {
+  return !preinitialization_transactions_.empty() && !GetTransaction()->IsRollingBack();
+}
+
 void Runtime::EnterTransactionMode() {
   DCHECK(IsAotCompiler());
   DCHECK(!IsActiveTransaction());
-  preinitialization_transaction_ = std::make_unique<Transaction>();
+  preinitialization_transactions_.push_back(std::make_unique<Transaction>());
 }
 
-void Runtime::EnterTransactionMode(mirror::Class* root) {
+void Runtime::EnterTransactionMode(bool strict, mirror::Class* root) {
   DCHECK(IsAotCompiler());
-  preinitialization_transaction_ = std::make_unique<Transaction>(root);
+  preinitialization_transactions_.push_back(std::make_unique<Transaction>(strict, root));
 }
 
 void Runtime::ExitTransactionMode() {
   DCHECK(IsAotCompiler());
-  preinitialization_transaction_ = nullptr;
+  DCHECK(IsActiveTransaction());
+  preinitialization_transactions_.pop_back();
 }
 
 void Runtime::RollbackAndExitTransactionMode() {
   DCHECK(IsAotCompiler());
   DCHECK(IsActiveTransaction());
-  std::unique_ptr<Transaction> rollback_transaction_= std::move(preinitialization_transaction_);
-  ExitTransactionMode();
-  rollback_transaction_->Rollback();
+  preinitialization_transactions_.back()->Rollback();
+  preinitialization_transactions_.pop_back();
 }
 
 bool Runtime::IsTransactionAborted() const {
@@ -2091,67 +2100,86 @@
     return false;
   } else {
     DCHECK(IsAotCompiler());
-    return preinitialization_transaction_->IsAborted();
+    return GetTransaction()->IsAborted();
   }
 }
 
+void Runtime::RollbackAllTransactions() {
+  // If transaction is aborted, all transactions will be kept in the list.
+  // Rollback and exit all of them.
+  while (IsActiveTransaction()) {
+    RollbackAndExitTransactionMode();
+  }
+}
+
+bool Runtime::IsActiveStrictTransactionMode() const {
+  return IsActiveTransaction() && GetTransaction()->IsStrict();
+}
+
+const std::unique_ptr<Transaction>& Runtime::GetTransaction() const {
+  DCHECK(!preinitialization_transactions_.empty());
+  return preinitialization_transactions_.back();
+}
+
 void Runtime::AbortTransactionAndThrowAbortError(Thread* self, const std::string& abort_message) {
   DCHECK(IsAotCompiler());
   DCHECK(IsActiveTransaction());
   // Throwing an exception may cause its class initialization. If we mark the transaction
   // aborted before that, we may warn with a false alarm. Throwing the exception before
   // marking the transaction aborted avoids that.
-  preinitialization_transaction_->ThrowAbortError(self, &abort_message);
-  preinitialization_transaction_->Abort(abort_message);
+  // But now the transaction can be nested, and abort the transaction will relax the constraints
+  // for constructing stack trace.
+  GetTransaction()->Abort(abort_message);
+  GetTransaction()->ThrowAbortError(self, &abort_message);
 }
 
 void Runtime::ThrowTransactionAbortError(Thread* self) {
   DCHECK(IsAotCompiler());
   DCHECK(IsActiveTransaction());
   // Passing nullptr means we rethrow an exception with the earlier transaction abort message.
-  preinitialization_transaction_->ThrowAbortError(self, nullptr);
+  GetTransaction()->ThrowAbortError(self, nullptr);
 }
 
 void Runtime::RecordWriteFieldBoolean(mirror::Object* obj, MemberOffset field_offset,
                                       uint8_t value, bool is_volatile) const {
   DCHECK(IsAotCompiler());
   DCHECK(IsActiveTransaction());
-  preinitialization_transaction_->RecordWriteFieldBoolean(obj, field_offset, value, is_volatile);
+  GetTransaction()->RecordWriteFieldBoolean(obj, field_offset, value, is_volatile);
 }
 
 void Runtime::RecordWriteFieldByte(mirror::Object* obj, MemberOffset field_offset,
                                    int8_t value, bool is_volatile) const {
   DCHECK(IsAotCompiler());
   DCHECK(IsActiveTransaction());
-  preinitialization_transaction_->RecordWriteFieldByte(obj, field_offset, value, is_volatile);
+  GetTransaction()->RecordWriteFieldByte(obj, field_offset, value, is_volatile);
 }
 
 void Runtime::RecordWriteFieldChar(mirror::Object* obj, MemberOffset field_offset,
                                    uint16_t value, bool is_volatile) const {
   DCHECK(IsAotCompiler());
   DCHECK(IsActiveTransaction());
-  preinitialization_transaction_->RecordWriteFieldChar(obj, field_offset, value, is_volatile);
+  GetTransaction()->RecordWriteFieldChar(obj, field_offset, value, is_volatile);
 }
 
 void Runtime::RecordWriteFieldShort(mirror::Object* obj, MemberOffset field_offset,
                                     int16_t value, bool is_volatile) const {
   DCHECK(IsAotCompiler());
   DCHECK(IsActiveTransaction());
-  preinitialization_transaction_->RecordWriteFieldShort(obj, field_offset, value, is_volatile);
+  GetTransaction()->RecordWriteFieldShort(obj, field_offset, value, is_volatile);
 }
 
 void Runtime::RecordWriteField32(mirror::Object* obj, MemberOffset field_offset,
                                  uint32_t value, bool is_volatile) const {
   DCHECK(IsAotCompiler());
   DCHECK(IsActiveTransaction());
-  preinitialization_transaction_->RecordWriteField32(obj, field_offset, value, is_volatile);
+  GetTransaction()->RecordWriteField32(obj, field_offset, value, is_volatile);
 }
 
 void Runtime::RecordWriteField64(mirror::Object* obj, MemberOffset field_offset,
                                  uint64_t value, bool is_volatile) const {
   DCHECK(IsAotCompiler());
   DCHECK(IsActiveTransaction());
-  preinitialization_transaction_->RecordWriteField64(obj, field_offset, value, is_volatile);
+  GetTransaction()->RecordWriteField64(obj, field_offset, value, is_volatile);
 }
 
 void Runtime::RecordWriteFieldReference(mirror::Object* obj,
@@ -2160,7 +2188,7 @@
                                         bool is_volatile) const {
   DCHECK(IsAotCompiler());
   DCHECK(IsActiveTransaction());
-  preinitialization_transaction_->RecordWriteFieldReference(obj,
+  GetTransaction()->RecordWriteFieldReference(obj,
                                                             field_offset,
                                                             value.Ptr(),
                                                             is_volatile);
@@ -2169,38 +2197,38 @@
 void Runtime::RecordWriteArray(mirror::Array* array, size_t index, uint64_t value) const {
   DCHECK(IsAotCompiler());
   DCHECK(IsActiveTransaction());
-  preinitialization_transaction_->RecordWriteArray(array, index, value);
+  GetTransaction()->RecordWriteArray(array, index, value);
 }
 
 void Runtime::RecordStrongStringInsertion(ObjPtr<mirror::String> s) const {
   DCHECK(IsAotCompiler());
   DCHECK(IsActiveTransaction());
-  preinitialization_transaction_->RecordStrongStringInsertion(s);
+  GetTransaction()->RecordStrongStringInsertion(s);
 }
 
 void Runtime::RecordWeakStringInsertion(ObjPtr<mirror::String> s) const {
   DCHECK(IsAotCompiler());
   DCHECK(IsActiveTransaction());
-  preinitialization_transaction_->RecordWeakStringInsertion(s);
+  GetTransaction()->RecordWeakStringInsertion(s);
 }
 
 void Runtime::RecordStrongStringRemoval(ObjPtr<mirror::String> s) const {
   DCHECK(IsAotCompiler());
   DCHECK(IsActiveTransaction());
-  preinitialization_transaction_->RecordStrongStringRemoval(s);
+  GetTransaction()->RecordStrongStringRemoval(s);
 }
 
 void Runtime::RecordWeakStringRemoval(ObjPtr<mirror::String> s) const {
   DCHECK(IsAotCompiler());
   DCHECK(IsActiveTransaction());
-  preinitialization_transaction_->RecordWeakStringRemoval(s);
+  GetTransaction()->RecordWeakStringRemoval(s);
 }
 
 void Runtime::RecordResolveString(ObjPtr<mirror::DexCache> dex_cache,
                                   dex::StringIndex string_idx) const {
   DCHECK(IsAotCompiler());
   DCHECK(IsActiveTransaction());
-  preinitialization_transaction_->RecordResolveString(dex_cache, string_idx);
+  GetTransaction()->RecordResolveString(dex_cache, string_idx);
 }
 
 void Runtime::SetFaultMessage(const std::string& message) {
@@ -2418,5 +2446,4 @@
     GetClassLinker()->VisitClasses(&visitor);
   }
 }
-
 }  // namespace art
diff --git a/runtime/runtime.h b/runtime/runtime.h
index 7e4b896..0c1344e 100644
--- a/runtime/runtime.h
+++ b/runtime/runtime.h
@@ -454,16 +454,17 @@
                        const std::string& profile_output_filename);
 
   // Transaction support.
-  bool IsActiveTransaction() const {
-    return preinitialization_transaction_ != nullptr;
-  }
+  bool IsActiveTransaction() const;
   void EnterTransactionMode();
-  void EnterTransactionMode(mirror::Class* root);
+  void EnterTransactionMode(bool strict, mirror::Class* root);
   void ExitTransactionMode();
+  void RollbackAllTransactions() REQUIRES_SHARED(Locks::mutator_lock_);
   // Transaction rollback and exit transaction are always done together, it's convenience to
   // do them in one function.
   void RollbackAndExitTransactionMode() REQUIRES_SHARED(Locks::mutator_lock_);
   bool IsTransactionAborted() const;
+  const std::unique_ptr<Transaction>& GetTransaction() const;
+  bool IsActiveStrictTransactionMode() const;
 
   void AbortTransactionAndThrowAbortError(Thread* self, const std::string& abort_message)
       REQUIRES_SHARED(Locks::mutator_lock_);
@@ -841,8 +842,11 @@
   // If true, then we dump the GC cumulative timings on shutdown.
   bool dump_gc_performance_on_shutdown_;
 
-  // Transaction used for pre-initializing classes at compilation time.
-  std::unique_ptr<Transaction> preinitialization_transaction_;
+  // Transactions used for pre-initializing classes at compilation time.
+  // Support nested transactions, maintain a list containing all transactions. Transactions are
+  // handled under a stack discipline. Because GC needs to go over all transactions, we choose list
+  // as substantial data structure instead of stack.
+  std::list<std::unique_ptr<Transaction>> preinitialization_transactions_;
 
   // If kNone, verification is disabled. kEnable by default.
   verifier::VerifyMode verify_;
diff --git a/runtime/transaction.cc b/runtime/transaction.cc
index 9e62aa6..e923aff 100644
--- a/runtime/transaction.cc
+++ b/runtime/transaction.cc
@@ -34,11 +34,15 @@
 static constexpr bool kEnableTransactionStats = false;
 
 Transaction::Transaction()
-  : log_lock_("transaction log lock", kTransactionLogLock), aborted_(false) {
+  : log_lock_("transaction log lock", kTransactionLogLock),
+    aborted_(false),
+    rolling_back_(false),
+    strict_(false) {
   CHECK(Runtime::Current()->IsAotCompiler());
 }
 
-Transaction::Transaction(mirror::Class* root) : Transaction() {
+Transaction::Transaction(bool strict, mirror::Class* root) : Transaction() {
+  strict_ = strict;
   root_ = root;
 }
 
@@ -101,11 +105,41 @@
   return aborted_;
 }
 
+bool Transaction::IsRollingBack() {
+  return rolling_back_;
+}
+
+bool Transaction::IsStrict() {
+  MutexLock mu(Thread::Current(), log_lock_);
+  return strict_;
+}
+
 const std::string& Transaction::GetAbortMessage() {
   MutexLock mu(Thread::Current(), log_lock_);
   return abort_message_;
 }
 
+bool Transaction::WriteConstraint(mirror::Object* obj, ArtField* field) {
+  MutexLock mu(Thread::Current(), log_lock_);
+  if (strict_  // no constraint for boot image
+      && field->IsStatic()  // no constraint instance updating
+      && obj != root_) {  // modifying other classes' static field, fail
+    return true;
+  }
+  return false;
+}
+
+bool Transaction::ReadConstraint(mirror::Object* obj, ArtField* field) {
+  DCHECK(field->IsStatic());
+  DCHECK(obj->IsClass());
+  MutexLock mu(Thread::Current(), log_lock_);
+  if (!strict_ ||   // no constraint for boot image
+      obj == root_) {  // self-updating, pass
+    return false;
+  }
+  return true;
+}
+
 void Transaction::RecordWriteFieldBoolean(mirror::Object* obj,
                                           MemberOffset field_offset,
                                           uint8_t value,
@@ -226,15 +260,17 @@
 }
 
 void Transaction::Rollback() {
-  CHECK(!Runtime::Current()->IsActiveTransaction());
   Thread* self = Thread::Current();
   self->AssertNoPendingException();
   MutexLock mu1(self, *Locks::intern_table_lock_);
   MutexLock mu2(self, log_lock_);
+  rolling_back_ = true;
+  CHECK(!Runtime::Current()->IsActiveTransaction());
   UndoObjectModifications();
   UndoArrayModifications();
   UndoInternStringTableModifications();
   UndoResolveStringModifications();
+  rolling_back_ = false;
 }
 
 void Transaction::UndoObjectModifications() {
@@ -416,7 +452,7 @@
                                             const FieldValue& field_value) const {
   // TODO We may want to abort a transaction while still being in transaction mode. In this case,
   // we'd need to disable the check.
-  constexpr bool kCheckTransaction = true;
+  constexpr bool kCheckTransaction = false;
   switch (field_value.kind) {
     case kBoolean:
       if (UNLIKELY(field_value.is_volatile)) {
@@ -595,30 +631,39 @@
                                            uint64_t value) const {
   // TODO We may want to abort a transaction while still being in transaction mode. In this case,
   // we'd need to disable the check.
+  constexpr bool kCheckTransaction = false;
   switch (array_type) {
     case Primitive::kPrimBoolean:
-      array->AsBooleanArray()->SetWithoutChecks<false>(index, static_cast<uint8_t>(value));
+      array->AsBooleanArray()->SetWithoutChecks<false, kCheckTransaction>(
+          index, static_cast<uint8_t>(value));
       break;
     case Primitive::kPrimByte:
-      array->AsByteArray()->SetWithoutChecks<false>(index, static_cast<int8_t>(value));
+      array->AsByteArray()->SetWithoutChecks<false, kCheckTransaction>(
+          index, static_cast<int8_t>(value));
       break;
     case Primitive::kPrimChar:
-      array->AsCharArray()->SetWithoutChecks<false>(index, static_cast<uint16_t>(value));
+      array->AsCharArray()->SetWithoutChecks<false, kCheckTransaction>(
+          index, static_cast<uint16_t>(value));
       break;
     case Primitive::kPrimShort:
-      array->AsShortArray()->SetWithoutChecks<false>(index, static_cast<int16_t>(value));
+      array->AsShortArray()->SetWithoutChecks<false, kCheckTransaction>(
+          index, static_cast<int16_t>(value));
       break;
     case Primitive::kPrimInt:
-      array->AsIntArray()->SetWithoutChecks<false>(index, static_cast<int32_t>(value));
+      array->AsIntArray()->SetWithoutChecks<false, kCheckTransaction>(
+          index, static_cast<int32_t>(value));
       break;
     case Primitive::kPrimFloat:
-      array->AsFloatArray()->SetWithoutChecks<false>(index, static_cast<float>(value));
+      array->AsFloatArray()->SetWithoutChecks<false, kCheckTransaction>(
+          index, static_cast<float>(value));
       break;
     case Primitive::kPrimLong:
-      array->AsLongArray()->SetWithoutChecks<false>(index, static_cast<int64_t>(value));
+      array->AsLongArray()->SetWithoutChecks<false, kCheckTransaction>(
+          index, static_cast<int64_t>(value));
       break;
     case Primitive::kPrimDouble:
-      array->AsDoubleArray()->SetWithoutChecks<false>(index, static_cast<double>(value));
+      array->AsDoubleArray()->SetWithoutChecks<false, kCheckTransaction>(
+          index, static_cast<double>(value));
       break;
     case Primitive::kPrimNot:
       LOG(FATAL) << "ObjectArray should be treated as Object";
diff --git a/runtime/transaction.h b/runtime/transaction.h
index 22518f6..4e9cde5 100644
--- a/runtime/transaction.h
+++ b/runtime/transaction.h
@@ -45,7 +45,7 @@
   static constexpr const char* kAbortExceptionSignature = "Ldalvik/system/TransactionAbortError;";
 
   Transaction();
-  explicit Transaction(mirror::Class* root);
+  explicit Transaction(bool strict, mirror::Class* root);
   ~Transaction();
 
   void Abort(const std::string& abort_message)
@@ -56,6 +56,15 @@
       REQUIRES_SHARED(Locks::mutator_lock_);
   bool IsAborted() REQUIRES(!log_lock_);
 
+  // If the transaction is rollbacking. Transactions will set this flag when they start rollbacking,
+  // because the nested transaction should be disabled when rollbacking to restore the memory.
+  bool IsRollingBack();
+
+  // If the transaction is in strict mode, then all access of static fields will be constrained,
+  // one class's clinit will not be allowed to read or modify another class's static fields, unless
+  // the transaction is aborted.
+  bool IsStrict() REQUIRES(!log_lock_);
+
   // Record object field changes.
   void RecordWriteFieldBoolean(mirror::Object* obj,
                                MemberOffset field_offset,
@@ -126,6 +135,14 @@
       REQUIRES(!log_lock_)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
+  bool ReadConstraint(mirror::Object* obj, ArtField* field)
+      REQUIRES(!log_lock_)
+      REQUIRES_SHARED(Locks::mutator_lock_);
+
+  bool WriteConstraint(mirror::Object* obj, ArtField* field)
+      REQUIRES(!log_lock_)
+      REQUIRES_SHARED(Locks::mutator_lock_);
+
  private:
   class ObjectLog : public ValueObject {
    public:
@@ -289,6 +306,8 @@
   std::list<InternStringLog> intern_string_logs_ GUARDED_BY(log_lock_);
   std::list<ResolveStringLog> resolve_string_logs_ GUARDED_BY(log_lock_);
   bool aborted_ GUARDED_BY(log_lock_);
+  bool rolling_back_;  // Single thread, no race.
+  bool strict_ GUARDED_BY(log_lock_);
   std::string abort_message_ GUARDED_BY(log_lock_);
   mirror::Class* root_ GUARDED_BY(log_lock_);
 
diff --git a/runtime/vdex_file.h b/runtime/vdex_file.h
index 0351fd3..63058cf 100644
--- a/runtime/vdex_file.h
+++ b/runtime/vdex_file.h
@@ -72,8 +72,8 @@
 
    private:
     static constexpr uint8_t kVdexMagic[] = { 'v', 'd', 'e', 'x' };
-    // Last update: Change method lookup.
-    static constexpr uint8_t kVdexVersion[] = { '0', '0', '9', '\0' };
+    // Last update: Use set for unverified_classes_.
+    static constexpr uint8_t kVdexVersion[] = { '0', '1', '0', '\0' };
 
     uint8_t magic_[4];
     uint8_t version_[4];
diff --git a/runtime/verifier/method_verifier.cc b/runtime/verifier/method_verifier.cc
index 6149f0d..312d8df 100644
--- a/runtime/verifier/method_verifier.cc
+++ b/runtime/verifier/method_verifier.cc
@@ -2899,10 +2899,12 @@
       ArtMethod* called_method = VerifyInvocationArgs(inst, type, is_range);
       const RegType* return_type = nullptr;
       if (called_method != nullptr) {
-        mirror::Class* return_type_class = called_method->GetReturnType(can_load_classes_);
+        ObjPtr<mirror::Class> return_type_class = can_load_classes_
+            ? called_method->ResolveReturnType()
+            : called_method->LookupResolvedReturnType();
         if (return_type_class != nullptr) {
           return_type = &FromClass(called_method->GetReturnTypeDescriptor(),
-                                   return_type_class,
+                                   return_type_class.Ptr(),
                                    return_type_class->CannotBeAssignedFromOtherTypes());
         } else {
           DCHECK(!can_load_classes_ || self_->IsExceptionPending());
@@ -2942,10 +2944,12 @@
       } else {
         is_constructor = called_method->IsConstructor();
         return_type_descriptor = called_method->GetReturnTypeDescriptor();
-        mirror::Class* return_type_class = called_method->GetReturnType(can_load_classes_);
+        ObjPtr<mirror::Class> return_type_class = can_load_classes_
+            ? called_method->ResolveReturnType()
+            : called_method->LookupResolvedReturnType();
         if (return_type_class != nullptr) {
           return_type = &FromClass(return_type_descriptor,
-                                   return_type_class,
+                                   return_type_class.Ptr(),
                                    return_type_class->CannotBeAssignedFromOtherTypes());
         } else {
           DCHECK(!can_load_classes_ || self_->IsExceptionPending());
@@ -3857,10 +3861,20 @@
   // TODO: Maybe we should not record dependency if the invoke type does not match the lookup type.
   VerifierDeps::MaybeRecordMethodResolution(*dex_file_, dex_method_idx, res_method);
 
+  bool must_fail = false;
+  // This is traditional and helps with screwy bytecode. It will tell you that, yes, a method
+  // exists, but that it's called incorrectly. This significantly helps debugging, as locally it's
+  // hard to see the differences.
+  // If we don't have res_method here we must fail. Just use this bool to make sure of that with a
+  // DCHECK.
   if (res_method == nullptr) {
+    must_fail = true;
     // Try to find the method also with the other type for better error reporting below
     // but do not store such bogus lookup result in the DexCache or VerifierDeps.
     if (klass->IsInterface()) {
+      // NB This is normally not really allowed but we want to get any static or private object
+      // methods for error message purposes. This will never be returned.
+      // TODO We might want to change the verifier to not require this.
       res_method = klass->FindClassMethod(dex_cache_.Get(), dex_method_idx, pointer_size);
     } else {
       // If there was an interface method with the same signature,
@@ -3918,6 +3932,20 @@
     }
   }
 
+  // Check specifically for non-public object methods being provided for interface dispatch. This
+  // can occur if we failed to find a method with FindInterfaceMethod but later find one with
+  // FindClassMethod for error message use.
+  if (method_type == METHOD_INTERFACE &&
+      res_method->GetDeclaringClass()->IsObjectClass() &&
+      !res_method->IsPublic()) {
+    Fail(VERIFY_ERROR_NO_METHOD) << "invoke-interface " << klass->PrettyDescriptor() << "."
+                                 << dex_file_->GetMethodName(method_id) << " "
+                                 << dex_file_->GetMethodSignature(method_id) << " resolved to "
+                                 << "non-public object method " << res_method->PrettyMethod() << " "
+                                 << "but non-public Object methods are excluded from interface "
+                                 << "method resolution.";
+    return nullptr;
+  }
   // Check if access is allowed.
   if (!referrer.CanAccessMember(res_method->GetDeclaringClass(), res_method->GetAccessFlags())) {
     Fail(VERIFY_ERROR_ACCESS_METHOD) << "illegal method access (call "
@@ -3946,6 +3974,13 @@
                                        "type of " << res_method->PrettyMethod();
     return nullptr;
   }
+  // Make sure we weren't expecting to fail.
+  DCHECK(!must_fail) << "invoke type (" << method_type << ")"
+                     << klass->PrettyDescriptor() << "."
+                     << dex_file_->GetMethodName(method_id) << " "
+                     << dex_file_->GetMethodSignature(method_id) << " unexpectedly resolved to "
+                     << res_method->PrettyMethod() << " without error. Initially this method was "
+                     << "not found so we were expecting to fail for some reason.";
   return res_method;
 }
 
@@ -5261,10 +5296,12 @@
 const RegType& MethodVerifier::GetMethodReturnType() {
   if (return_type_ == nullptr) {
     if (mirror_method_ != nullptr) {
-      mirror::Class* return_type_class = mirror_method_->GetReturnType(can_load_classes_);
+      ObjPtr<mirror::Class> return_type_class = can_load_classes_
+          ? mirror_method_->ResolveReturnType()
+          : mirror_method_->LookupResolvedReturnType();
       if (return_type_class != nullptr) {
         return_type_ = &FromClass(mirror_method_->GetReturnTypeDescriptor(),
-                                  return_type_class,
+                                  return_type_class.Ptr(),
                                   return_type_class->CannotBeAssignedFromOtherTypes());
       } else {
         DCHECK(!can_load_classes_ || self_->IsExceptionPending());
diff --git a/runtime/verifier/verifier_deps.cc b/runtime/verifier/verifier_deps.cc
index 112eec8..0481f24 100644
--- a/runtime/verifier/verifier_deps.cc
+++ b/runtime/verifier/verifier_deps.cc
@@ -33,7 +33,8 @@
 namespace art {
 namespace verifier {
 
-VerifierDeps::VerifierDeps(const std::vector<const DexFile*>& dex_files) {
+VerifierDeps::VerifierDeps(const std::vector<const DexFile*>& dex_files, bool output_only)
+    : output_only_(output_only) {
   for (const DexFile* dex_file : dex_files) {
     DCHECK(GetDexFileDeps(*dex_file) == nullptr);
     std::unique_ptr<DexFileDeps> deps(new DexFileDeps());
@@ -41,6 +42,9 @@
   }
 }
 
+VerifierDeps::VerifierDeps(const std::vector<const DexFile*>& dex_files)
+    : VerifierDeps(dex_files, /*output_only*/ true) {}
+
 void VerifierDeps::MergeWith(const VerifierDeps& other,
                              const std::vector<const DexFile*>& dex_files) {
   DCHECK(dex_deps_.size() == other.dex_deps_.size());
@@ -55,9 +59,7 @@
     MergeSets(my_deps->classes_, other_deps.classes_);
     MergeSets(my_deps->fields_, other_deps.fields_);
     MergeSets(my_deps->methods_, other_deps.methods_);
-    for (dex::TypeIndex entry : other_deps.unverified_classes_) {
-      my_deps->unverified_classes_.push_back(entry);
-    }
+    MergeSets(my_deps->unverified_classes_, other_deps.unverified_classes_);
   }
 }
 
@@ -503,7 +505,7 @@
   VerifierDeps* thread_deps = GetThreadLocalVerifierDeps();
   if (thread_deps != nullptr) {
     DexFileDeps* dex_deps = thread_deps->GetDexFileDeps(dex_file);
-    dex_deps->unverified_classes_.push_back(type_idx);
+    dex_deps->unverified_classes_.insert(type_idx);
   }
 }
 
@@ -582,6 +584,16 @@
   return dex::StringIndex(in);
 }
 
+// TODO: Clean this up, if we use a template arg here it confuses the compiler.
+static inline void EncodeTuple(std::vector<uint8_t>* out, const dex::TypeIndex& t) {
+  EncodeUnsignedLeb128(out, Encode(t));
+}
+
+// TODO: Clean this up, if we use a template arg here it confuses the compiler.
+static inline void DecodeTuple(const uint8_t** in, const uint8_t* end, dex::TypeIndex* t) {
+  *t = Decode<dex::TypeIndex>(DecodeUint32WithOverflowCheck(in, end));
+}
+
 template<typename T1, typename T2>
 static inline void EncodeTuple(std::vector<uint8_t>* out, const std::tuple<T1, T2>& t) {
   EncodeUnsignedLeb128(out, Encode(std::get<0>(t)));
@@ -688,13 +700,13 @@
     EncodeSet(buffer, deps.classes_);
     EncodeSet(buffer, deps.fields_);
     EncodeSet(buffer, deps.methods_);
-    EncodeUint16Vector(buffer, deps.unverified_classes_);
+    EncodeSet(buffer, deps.unverified_classes_);
   }
 }
 
 VerifierDeps::VerifierDeps(const std::vector<const DexFile*>& dex_files,
                            ArrayRef<const uint8_t> data)
-    : VerifierDeps(dex_files) {
+    : VerifierDeps(dex_files, /*output_only*/ false) {
   if (data.empty()) {
     // Return eagerly, as the first thing we expect from VerifierDeps data is
     // the number of created strings, even if there is no dependency.
@@ -711,7 +723,7 @@
     DecodeSet(&data_start, data_end, &deps->classes_);
     DecodeSet(&data_start, data_end, &deps->fields_);
     DecodeSet(&data_start, data_end, &deps->methods_);
-    DecodeUint16Vector(&data_start, data_end, &deps->unverified_classes_);
+    DecodeSet(&data_start, data_end, &deps->unverified_classes_);
   }
   CHECK_LE(data_start, data_end);
 }
diff --git a/runtime/verifier/verifier_deps.h b/runtime/verifier/verifier_deps.h
index b883a9e..4069a11 100644
--- a/runtime/verifier/verifier_deps.h
+++ b/runtime/verifier/verifier_deps.h
@@ -117,10 +117,14 @@
   bool ValidateDependencies(Handle<mirror::ClassLoader> class_loader, Thread* self) const
       REQUIRES_SHARED(Locks::mutator_lock_);
 
-  const std::vector<dex::TypeIndex>& GetUnverifiedClasses(const DexFile& dex_file) const {
+  const std::set<dex::TypeIndex>& GetUnverifiedClasses(const DexFile& dex_file) const {
     return GetDexFileDeps(dex_file)->unverified_classes_;
   }
 
+  bool OutputOnly() const {
+    return output_only_;
+  }
+
  private:
   static constexpr uint16_t kUnresolvedMarker = static_cast<uint16_t>(-1);
 
@@ -193,11 +197,13 @@
     std::set<MethodResolution> methods_;
 
     // List of classes that were not fully verified in that dex file.
-    std::vector<dex::TypeIndex> unverified_classes_;
+    std::set<dex::TypeIndex> unverified_classes_;
 
     bool Equals(const DexFileDeps& rhs) const;
   };
 
+  VerifierDeps(const std::vector<const DexFile*>& dex_files, bool output_only);
+
   // Finds the DexFileDep instance associated with `dex_file`, or nullptr if
   // `dex_file` is not reported as being compiled.
   DexFileDeps* GetDexFileDeps(const DexFile& dex_file);
@@ -321,6 +327,9 @@
   // Map from DexFiles into dependencies collected from verification of their methods.
   std::map<const DexFile*, std::unique_ptr<DexFileDeps>> dex_deps_;
 
+  // Output only signifies if we are using the verifier deps to verify or just to generate them.
+  const bool output_only_;
+
   friend class VerifierDepsTest;
   ART_FRIEND_TEST(VerifierDepsTest, StringToId);
   ART_FRIEND_TEST(VerifierDepsTest, EncodeDecode);
diff --git a/runtime/simulator/Android.bp b/simulator/Android.bp
similarity index 60%
rename from runtime/simulator/Android.bp
rename to simulator/Android.bp
index 03e3f15..a399289 100644
--- a/runtime/simulator/Android.bp
+++ b/simulator/Android.bp
@@ -14,6 +14,12 @@
 // limitations under the License.
 //
 
+cc_library_headers {
+    name: "libart_simulator_headers",
+    host_supported: true,
+    export_include_dirs: ["include"],
+}
+
 cc_defaults {
     name: "libart_simulator_defaults",
     host_supported: true,
@@ -29,8 +35,8 @@
         "liblog",
     ],
     cflags: ["-DVIXL_INCLUDE_SIMULATOR_AARCH64"],
-    export_include_dirs: ["."],
-    include_dirs: ["art/runtime"],
+
+    header_libs: ["libart_simulator_headers"],
 }
 
 art_cc_library {
@@ -53,3 +59,38 @@
         "libvixld-arm64",
     ],
 }
+
+cc_defaults {
+    name: "libart_simulator_container_defaults",
+    host_supported: true,
+
+    defaults: ["art_defaults"],
+    srcs: [
+        "code_simulator_container.cc",
+    ],
+    shared_libs: [
+        "libbase",
+    ],
+
+    header_libs: ["libart_simulator_headers"],
+    export_include_dirs: ["."],  // TODO: Consider a proper separation.
+}
+
+art_cc_library {
+    name: "libart-simulator-container",
+    defaults: ["libart_simulator_container_defaults"],
+    shared_libs: [
+        "libart",
+    ],
+}
+
+art_cc_library {
+    name: "libartd-simulator-container",
+    defaults: [
+        "art_debug_defaults",
+        "libart_simulator_container_defaults",
+    ],
+    shared_libs: [
+        "libartd",
+    ],
+}
diff --git a/runtime/simulator/code_simulator.cc b/simulator/code_simulator.cc
similarity index 92%
rename from runtime/simulator/code_simulator.cc
rename to simulator/code_simulator.cc
index 1a11160..e653dfc 100644
--- a/runtime/simulator/code_simulator.cc
+++ b/simulator/code_simulator.cc
@@ -14,8 +14,9 @@
  * limitations under the License.
  */
 
-#include "simulator/code_simulator.h"
-#include "simulator/code_simulator_arm64.h"
+#include "code_simulator.h"
+
+#include "code_simulator_arm64.h"
 
 namespace art {
 
diff --git a/runtime/simulator/code_simulator_arm64.cc b/simulator/code_simulator_arm64.cc
similarity index 97%
rename from runtime/simulator/code_simulator_arm64.cc
rename to simulator/code_simulator_arm64.cc
index c7ad1fd..939d2e2 100644
--- a/runtime/simulator/code_simulator_arm64.cc
+++ b/simulator/code_simulator_arm64.cc
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#include "simulator/code_simulator_arm64.h"
+#include "code_simulator_arm64.h"
 
 #include "base/logging.h"
 
diff --git a/runtime/simulator/code_simulator_arm64.h b/simulator/code_simulator_arm64.h
similarity index 88%
rename from runtime/simulator/code_simulator_arm64.h
rename to simulator/code_simulator_arm64.h
index 59ea34f..0542593 100644
--- a/runtime/simulator/code_simulator_arm64.h
+++ b/simulator/code_simulator_arm64.h
@@ -14,11 +14,10 @@
  * limitations under the License.
  */
 
-#ifndef ART_RUNTIME_SIMULATOR_CODE_SIMULATOR_ARM64_H_
-#define ART_RUNTIME_SIMULATOR_CODE_SIMULATOR_ARM64_H_
+#ifndef ART_SIMULATOR_CODE_SIMULATOR_ARM64_H_
+#define ART_SIMULATOR_CODE_SIMULATOR_ARM64_H_
 
 #include "memory"
-#include "simulator/code_simulator.h"
 
 // TODO(VIXL): Make VIXL compile with -Wshadow.
 #pragma GCC diagnostic push
@@ -26,6 +25,8 @@
 #include "aarch64/simulator-aarch64.h"
 #pragma GCC diagnostic pop
 
+#include "code_simulator.h"
+
 namespace art {
 namespace arm64 {
 
@@ -55,4 +56,4 @@
 }  // namespace arm64
 }  // namespace art
 
-#endif  // ART_RUNTIME_SIMULATOR_CODE_SIMULATOR_ARM64_H_
+#endif  // ART_SIMULATOR_CODE_SIMULATOR_ARM64_H_
diff --git a/runtime/code_simulator_container.cc b/simulator/code_simulator_container.cc
similarity index 98%
rename from runtime/code_simulator_container.cc
rename to simulator/code_simulator_container.cc
index d884c58..a5f05dc 100644
--- a/runtime/code_simulator_container.cc
+++ b/simulator/code_simulator_container.cc
@@ -17,6 +17,8 @@
 #include <dlfcn.h>
 
 #include "code_simulator_container.h"
+
+#include "code_simulator.h"
 #include "globals.h"
 
 namespace art {
diff --git a/runtime/code_simulator_container.h b/simulator/code_simulator_container.h
similarity index 87%
rename from runtime/code_simulator_container.h
rename to simulator/code_simulator_container.h
index 10178ba..31a915e 100644
--- a/runtime/code_simulator_container.h
+++ b/simulator/code_simulator_container.h
@@ -14,15 +14,16 @@
  * limitations under the License.
  */
 
-#ifndef ART_RUNTIME_CODE_SIMULATOR_CONTAINER_H_
-#define ART_RUNTIME_CODE_SIMULATOR_CONTAINER_H_
+#ifndef ART_SIMULATOR_CODE_SIMULATOR_CONTAINER_H_
+#define ART_SIMULATOR_CODE_SIMULATOR_CONTAINER_H_
 
 #include "arch/instruction_set.h"
 #include "base/logging.h"
-#include "simulator/code_simulator.h"
 
 namespace art {
 
+class CodeSimulator;
+
 // This container dynamically opens and closes libart-simulator.
 class CodeSimulatorContainer {
  public:
@@ -52,4 +53,4 @@
 
 }  // namespace art
 
-#endif  // ART_RUNTIME_CODE_SIMULATOR_CONTAINER_H_
+#endif  // ART_SIMULATOR_CODE_SIMULATOR_CONTAINER_H_
diff --git a/runtime/simulator/code_simulator.h b/simulator/include/code_simulator.h
similarity index 89%
rename from runtime/simulator/code_simulator.h
rename to simulator/include/code_simulator.h
index bd48909..256ab23 100644
--- a/runtime/simulator/code_simulator.h
+++ b/simulator/include/code_simulator.h
@@ -14,8 +14,8 @@
  * limitations under the License.
  */
 
-#ifndef ART_RUNTIME_SIMULATOR_CODE_SIMULATOR_H_
-#define ART_RUNTIME_SIMULATOR_CODE_SIMULATOR_H_
+#ifndef ART_SIMULATOR_INCLUDE_CODE_SIMULATOR_H_
+#define ART_SIMULATOR_INCLUDE_CODE_SIMULATOR_H_
 
 #include "arch/instruction_set.h"
 
@@ -43,4 +43,4 @@
 
 }  // namespace art
 
-#endif  // ART_RUNTIME_SIMULATOR_CODE_SIMULATOR_H_
+#endif  // ART_SIMULATOR_INCLUDE_CODE_SIMULATOR_H_
diff --git a/test/075-verification-error/expected.txt b/test/075-verification-error/expected.txt
index 6e4f584..7ccc32c 100644
--- a/test/075-verification-error/expected.txt
+++ b/test/075-verification-error/expected.txt
@@ -10,3 +10,4 @@
 Got expected IllegalAccessError (meth-class)
 Got expected IllegalAccessError (field-class)
 Got expected IllegalAccessError (meth-meth)
+Got expected IncompatibleClassChangeError (interface)
diff --git a/test/075-verification-error/src/BadIfaceImpl.java b/test/075-verification-error/src/BadIfaceImpl.java
new file mode 100644
index 0000000..fa2a970
--- /dev/null
+++ b/test/075-verification-error/src/BadIfaceImpl.java
@@ -0,0 +1,17 @@
+/*
+ * 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 BadIfaceImpl implements BadInterface { }
diff --git a/test/075-verification-error/src/BadInterface.java b/test/075-verification-error/src/BadInterface.java
new file mode 100644
index 0000000..439aba4
--- /dev/null
+++ b/test/075-verification-error/src/BadInterface.java
@@ -0,0 +1,21 @@
+/*
+ * 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 interface BadInterface {
+  public default Object internalClone() {
+    throw new Error("Should not be called");
+  }
+}
diff --git a/test/075-verification-error/src/Main.java b/test/075-verification-error/src/Main.java
index 3f2881e..13aeaee 100644
--- a/test/075-verification-error/src/Main.java
+++ b/test/075-verification-error/src/Main.java
@@ -28,6 +28,20 @@
         testClassNewInstance();
         testMissingStuff();
         testBadAccess();
+        testBadInterfaceMethod();
+     }
+    /**
+     * Try to create and invoke a non-existant interface method.
+     */
+    static void testBadInterfaceMethod() {
+        BadInterface badiface = new BadIfaceImpl();
+        try {
+            badiface.internalClone();
+        } catch (IncompatibleClassChangeError icce) {
+            // TODO b/64274113 This should really be an NSME
+            System.out.println("Got expected IncompatibleClassChangeError (interface)");
+            if (VERBOSE) System.out.println("--- " + icce);
+        }
     }
 
     /**
diff --git a/test/075-verification-error/src2/BadInterface.java b/test/075-verification-error/src2/BadInterface.java
new file mode 100644
index 0000000..5d939cb
--- /dev/null
+++ b/test/075-verification-error/src2/BadInterface.java
@@ -0,0 +1,17 @@
+/*
+ * 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 interface BadInterface { }
+
diff --git a/test/1338-gc-no-los/expected.txt b/test/1338-gc-no-los/expected.txt
new file mode 100644
index 0000000..36bec43
--- /dev/null
+++ b/test/1338-gc-no-los/expected.txt
@@ -0,0 +1 @@
+131072 200 true
diff --git a/test/1338-gc-no-los/info.txt b/test/1338-gc-no-los/info.txt
new file mode 100644
index 0000000..2e27357
--- /dev/null
+++ b/test/1338-gc-no-los/info.txt
@@ -0,0 +1 @@
+Test that the GC works with no large object space. Regression test for b/64393515.
\ No newline at end of file
diff --git a/test/1338-gc-no-los/run b/test/1338-gc-no-los/run
new file mode 100755
index 0000000..f3c43fb
--- /dev/null
+++ b/test/1338-gc-no-los/run
@@ -0,0 +1,16 @@
+#!/bin/bash
+#
+# Copyright 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+./default-run "$@" --runtime-option -XX:LargeObjectSpace=disabled
diff --git a/test/1338-gc-no-los/src-art/Main.java b/test/1338-gc-no-los/src-art/Main.java
new file mode 100644
index 0000000..662fa7e
--- /dev/null
+++ b/test/1338-gc-no-los/src-art/Main.java
@@ -0,0 +1,39 @@
+/*
+ * 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 dalvik.system.VMRuntime;
+import java.lang.ref.WeakReference;
+import java.lang.reflect.Method;
+
+public class Main {
+    public static void main(String[] args) {
+        try {
+            // Allocate a large object.
+            byte[] arr = new byte[128 * 1024];
+            // Allocate a non movable object.
+            byte[] arr2 = (byte[])VMRuntime.getRuntime().newNonMovableArray(Byte.TYPE, 200);
+            // Put the array in a weak reference so that IsMarked is called by the GC.
+            WeakReference weakRef = new WeakReference(arr2);
+            // Do a GC.
+            Runtime.getRuntime().gc();
+            arr[0] = 1;
+            arr2[0] = 1;
+            System.out.println(arr.length + " " + arr2.length + " " + (weakRef.get() != null));
+        } catch (Exception e) {
+            System.out.println(e);
+        }
+    }
+}
diff --git a/test/162-method-resolution/expected.txt b/test/162-method-resolution/expected.txt
index 1bf39c9..9b48a4c 100644
--- a/test/162-method-resolution/expected.txt
+++ b/test/162-method-resolution/expected.txt
@@ -41,3 +41,6 @@
 Calling Test9User2.test():
 Caught java.lang.reflect.InvocationTargetException
   caused by java.lang.IncompatibleClassChangeError
+Calling Test10User.test():
+Caught java.lang.reflect.InvocationTargetException
+  caused by java.lang.IncompatibleClassChangeError
diff --git a/test/162-method-resolution/jasmin/Test10Base.j b/test/162-method-resolution/jasmin/Test10Base.j
new file mode 100644
index 0000000..628f38d
--- /dev/null
+++ b/test/162-method-resolution/jasmin/Test10Base.j
@@ -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.
+
+.class                   public Test10Base
+.super                   java/lang/Object
+.implements              Test10Interface
+
+.method                  public <init>()V
+   .limit stack          1
+   .limit locals         1
+   aload_0
+   invokespecial         java/lang/Object/<init>()V
+   return
+.end method
diff --git a/test/162-method-resolution/jasmin/Test10User.j b/test/162-method-resolution/jasmin/Test10User.j
new file mode 100644
index 0000000..6beadab
--- /dev/null
+++ b/test/162-method-resolution/jasmin/Test10User.j
@@ -0,0 +1,36 @@
+; Copyright (C) 2017 The Android Open Source Project
+;
+; Licensed under the Apache License, Version 2.0 (the "License");
+; you may not use this file except in compliance with the License.
+; You may obtain a copy of the License at
+;
+;      http://www.apache.org/licenses/LICENSE-2.0
+;
+; Unless required by applicable law or agreed to in writing, software
+; distributed under the License is distributed on an "AS IS" BASIS,
+; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+; See the License for the specific language governing permissions and
+; limitations under the License.
+
+.class public Test10User
+.super java/lang/Object
+
+.method public static test()V
+    .limit stack 3
+    .limit locals 3
+    new Test10Base
+    dup
+    invokespecial Test10Base.<init>()V
+    invokestatic  Test10User.doInvoke(LTest10Interface;)V
+    return
+.end method
+
+.method public static doInvoke(LTest10Interface;)V
+    .limit stack 3
+    .limit locals 3
+    aload_0
+    invokeinterface Test10Interface.clone()Ljava.lang.Object; 1
+    pop
+    return
+.end method
+
diff --git a/test/162-method-resolution/multidex.jpp b/test/162-method-resolution/multidex.jpp
index 22e3aee..5722f7f 100644
--- a/test/162-method-resolution/multidex.jpp
+++ b/test/162-method-resolution/multidex.jpp
@@ -112,6 +112,16 @@
   @@com.android.jack.annotations.ForceInMainDex
   class Test9User2
 
+Test10Base:
+  @@com.android.jack.annotations.ForceInMainDex
+  class Test10Base
+Test10Interface:
+  @@com.android.jack.annotations.ForceInMainDex
+  class Test10Interface
+Test10User:
+  @@com.android.jack.annotations.ForceInMainDex
+  class Test10User
+
 Main:
   @@com.android.jack.annotations.ForceInMainDex
   class Main
diff --git a/test/162-method-resolution/src/Main.java b/test/162-method-resolution/src/Main.java
index fa95aa7..864c878 100644
--- a/test/162-method-resolution/src/Main.java
+++ b/test/162-method-resolution/src/Main.java
@@ -36,6 +36,7 @@
             test7();
             test8();
             test9();
+            test10();
 
             // TODO: How to test that interface method resolution returns the unique
             // maximally-specific non-abstract superinterface method if there is one?
@@ -376,6 +377,31 @@
         invokeUserTest("Test9User2");
     }
 
+    /*
+     * Test10
+     * -----
+     * Tested function:
+     *     public class Test10Base implements Test10Interface { }
+     *     public interface Test10Interface { }
+     * Tested invokes:
+     *     invoke-interface Test10Interface.clone()Ljava/lang/Object; from Test10Caller in first dex
+     *         TODO b/64274113 This should throw a NSME (JLS 13.4.12) but actually throws an ICCE.
+     *         expected: Throws NoSuchMethodError (JLS 13.4.12)
+     *         actual: Throws IncompatibleClassChangeError
+     *
+     * This test is simulating compiling Test10Interface with "public Object clone()" method, along
+     * with every other class. Then we delete "clone" from Test10Interface only, which under JLS
+     * 13.4.12 is expected to be binary incompatible and throw a NoSuchMethodError.
+     *
+     * Files:
+     *   jasmin/Test10Base.j          - implements Test10Interface
+     *   jasmin/Test10Interface.java  - defines empty interface
+     *   jasmin/Test10User.j          - invokeinterface Test10Interface.clone()Ljava/lang/Object;
+     */
+    private static void test10() throws Exception {
+        invokeUserTest("Test10User");
+    }
+
     private static void invokeUserTest(String userName) throws Exception {
         System.out.println("Calling " + userName + ".test():");
         try {
diff --git a/test/162-method-resolution/src/Test10Interface.java b/test/162-method-resolution/src/Test10Interface.java
new file mode 100644
index 0000000..3c75ea5
--- /dev/null
+++ b/test/162-method-resolution/src/Test10Interface.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 interface Test10Interface { }
+
diff --git a/runtime/openjdkjvm/MODULE_LICENSE_GPL_WITH_CLASSPATH_EXCEPTION b/test/1911-get-local-var-table/expected.txt
similarity index 100%
copy from runtime/openjdkjvm/MODULE_LICENSE_GPL_WITH_CLASSPATH_EXCEPTION
copy to test/1911-get-local-var-table/expected.txt
diff --git a/test/1911-get-local-var-table/info.txt b/test/1911-get-local-var-table/info.txt
new file mode 100644
index 0000000..43955e5
--- /dev/null
+++ b/test/1911-get-local-var-table/info.txt
@@ -0,0 +1 @@
+Tests getting local variable table from JVMTI
diff --git a/test/1911-get-local-var-table/run b/test/1911-get-local-var-table/run
new file mode 100755
index 0000000..51875a7
--- /dev/null
+++ b/test/1911-get-local-var-table/run
@@ -0,0 +1,18 @@
+#!/bin/bash
+#
+# Copyright 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Ask for stack traces to be dumped to a file rather than to stdout.
+./default-run "$@" --jvmti
diff --git a/test/1911-get-local-var-table/src/Main.java b/test/1911-get-local-var-table/src/Main.java
new file mode 100644
index 0000000..4e0c9e4
--- /dev/null
+++ b/test/1911-get-local-var-table/src/Main.java
@@ -0,0 +1,21 @@
+/*
+ * 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 {
+    art.Test1911.run();
+  }
+}
diff --git a/test/1911-get-local-var-table/src/art/Breakpoint.java b/test/1911-get-local-var-table/src/art/Breakpoint.java
new file mode 100644
index 0000000..bbb89f7
--- /dev/null
+++ b/test/1911-get-local-var-table/src/art/Breakpoint.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package art;
+
+import java.lang.reflect.Executable;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.Objects;
+
+public class Breakpoint {
+  public static class Manager {
+    public static class BP {
+      public final Executable method;
+      public final long location;
+
+      public BP(Executable method) {
+        this(method, getStartLocation(method));
+      }
+
+      public BP(Executable method, long location) {
+        this.method = method;
+        this.location = location;
+      }
+
+      @Override
+      public boolean equals(Object other) {
+        return (other instanceof BP) &&
+            method.equals(((BP)other).method) &&
+            location == ((BP)other).location;
+      }
+
+      @Override
+      public String toString() {
+        return method.toString() + " @ " + getLine();
+      }
+
+      @Override
+      public int hashCode() {
+        return Objects.hash(method, location);
+      }
+
+      public int getLine() {
+        try {
+          LineNumber[] lines = getLineNumberTable(method);
+          int best = -1;
+          for (LineNumber l : lines) {
+            if (l.location > location) {
+              break;
+            } else {
+              best = l.line;
+            }
+          }
+          return best;
+        } catch (Exception e) {
+          return -1;
+        }
+      }
+    }
+
+    private Set<BP> breaks = new HashSet<>();
+
+    public void setBreakpoints(BP... bs) {
+      for (BP b : bs) {
+        if (breaks.add(b)) {
+          Breakpoint.setBreakpoint(b.method, b.location);
+        }
+      }
+    }
+    public void setBreakpoint(Executable method, long location) {
+      setBreakpoints(new BP(method, location));
+    }
+
+    public void clearBreakpoints(BP... bs) {
+      for (BP b : bs) {
+        if (breaks.remove(b)) {
+          Breakpoint.clearBreakpoint(b.method, b.location);
+        }
+      }
+    }
+    public void clearBreakpoint(Executable method, long location) {
+      clearBreakpoints(new BP(method, location));
+    }
+
+    public void clearAllBreakpoints() {
+      clearBreakpoints(breaks.toArray(new BP[0]));
+    }
+  }
+
+  public static void startBreakpointWatch(Class<?> methodClass,
+                                          Executable breakpointReached,
+                                          Thread thr) {
+    startBreakpointWatch(methodClass, breakpointReached, false, thr);
+  }
+
+  /**
+   * Enables the trapping of breakpoint events.
+   *
+   * If allowRecursive == true then breakpoints will be sent even if one is currently being handled.
+   */
+  public static native void startBreakpointWatch(Class<?> methodClass,
+                                                 Executable breakpointReached,
+                                                 boolean allowRecursive,
+                                                 Thread thr);
+  public static native void stopBreakpointWatch(Thread thr);
+
+  public static final class LineNumber implements Comparable<LineNumber> {
+    public final long location;
+    public final int line;
+
+    private LineNumber(long loc, int line) {
+      this.location = loc;
+      this.line = line;
+    }
+
+    public boolean equals(Object other) {
+      return other instanceof LineNumber && ((LineNumber)other).line == line &&
+          ((LineNumber)other).location == location;
+    }
+
+    public int compareTo(LineNumber other) {
+      int v = Integer.valueOf(line).compareTo(Integer.valueOf(other.line));
+      if (v != 0) {
+        return v;
+      } else {
+        return Long.valueOf(location).compareTo(Long.valueOf(other.location));
+      }
+    }
+  }
+
+  public static native void setBreakpoint(Executable m, long loc);
+  public static void setBreakpoint(Executable m, LineNumber l) {
+    setBreakpoint(m, l.location);
+  }
+
+  public static native void clearBreakpoint(Executable m, long loc);
+  public static void clearBreakpoint(Executable m, LineNumber l) {
+    clearBreakpoint(m, l.location);
+  }
+
+  private static native Object[] getLineNumberTableNative(Executable m);
+  public static LineNumber[] getLineNumberTable(Executable m) {
+    Object[] nativeTable = getLineNumberTableNative(m);
+    long[] location = (long[])(nativeTable[0]);
+    int[] lines = (int[])(nativeTable[1]);
+    if (lines.length != location.length) {
+      throw new Error("Lines and locations have different lengths!");
+    }
+    LineNumber[] out = new LineNumber[lines.length];
+    for (int i = 0; i < lines.length; i++) {
+      out[i] = new LineNumber(location[i], lines[i]);
+    }
+    return out;
+  }
+
+  public static native long getStartLocation(Executable m);
+
+  public static int locationToLine(Executable m, long location) {
+    try {
+      Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m);
+      int best = -1;
+      for (Breakpoint.LineNumber l : lines) {
+        if (l.location > location) {
+          break;
+        } else {
+          best = l.line;
+        }
+      }
+      return best;
+    } catch (Exception e) {
+      return -1;
+    }
+  }
+
+  public static long lineToLocation(Executable m, int line) throws Exception {
+    try {
+      Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m);
+      for (Breakpoint.LineNumber l : lines) {
+        if (l.line == line) {
+          return l.location;
+        }
+      }
+      throw new Exception("Unable to find line " + line + " in " + m);
+    } catch (Exception e) {
+      throw new Exception("Unable to get line number info for " + m, e);
+    }
+  }
+}
+
diff --git a/test/1911-get-local-var-table/src/art/Locals.java b/test/1911-get-local-var-table/src/art/Locals.java
new file mode 100644
index 0000000..22e21be
--- /dev/null
+++ b/test/1911-get-local-var-table/src/art/Locals.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package art;
+
+import java.lang.reflect.Executable;
+import java.util.Objects;
+
+public class Locals {
+  public static native void EnableLocalVariableAccess();
+
+  public static class VariableDescription {
+    public final long start_location;
+    public final int length;
+    public final String name;
+    public final String signature;
+    public final String generic_signature;
+    public final int slot;
+
+    public VariableDescription(
+        long start, int length, String name, String sig, String gen_sig, int slot) {
+      this.start_location = start;
+      this.length = length;
+      this.name = name;
+      this.signature = sig;
+      this.generic_signature = gen_sig;
+      this.slot = slot;
+    }
+
+    @Override
+    public String toString() {
+      return String.format(
+          "VariableDescription { " +
+            "Sig: '%s', Name: '%s', Gen_sig: '%s', slot: %d, start: %d, len: %d" +
+          "}",
+          this.signature,
+          this.name,
+          this.generic_signature,
+          this.slot,
+          this.start_location,
+          this.length);
+    }
+    public boolean equals(Object other) {
+      if (!(other instanceof VariableDescription)) {
+        return false;
+      } else {
+        VariableDescription v = (VariableDescription)other;
+        return Objects.equals(v.signature, signature) &&
+            Objects.equals(v.name, name) &&
+            Objects.equals(v.generic_signature, generic_signature) &&
+            v.slot == slot &&
+            v.start_location == start_location &&
+            v.length == length;
+      }
+    }
+    public int hashCode() {
+      return Objects.hash(this.signature, this.name, this.generic_signature, this.slot,
+          this.start_location, this.length);
+    }
+  }
+
+  public static native VariableDescription[] GetLocalVariableTable(Executable e);
+
+  public static VariableDescription GetVariableAtLine(
+      Executable e, String name, String sig, int line) throws Exception {
+    return GetVariableAtLocation(e, name, sig, Breakpoint.lineToLocation(e, line));
+  }
+
+  public static VariableDescription GetVariableAtLocation(
+      Executable e, String name, String sig, long loc) {
+    VariableDescription[] vars = GetLocalVariableTable(e);
+    for (VariableDescription var : vars) {
+      if (var.start_location <= loc &&
+          var.length + var.start_location > loc &&
+          var.name.equals(name) &&
+          var.signature.equals(sig)) {
+        return var;
+      }
+    }
+    throw new Error(
+        "Unable to find variable " + name + " (sig: " + sig + ") in " + e + " at loc " + loc);
+  }
+
+  public static native int GetLocalVariableInt(Thread thr, int depth, int slot);
+  public static native long GetLocalVariableLong(Thread thr, int depth, int slot);
+  public static native float GetLocalVariableFloat(Thread thr, int depth, int slot);
+  public static native double GetLocalVariableDouble(Thread thr, int depth, int slot);
+  public static native Object GetLocalVariableObject(Thread thr, int depth, int slot);
+  public static native Object GetLocalInstance(Thread thr, int depth);
+
+  public static void SetLocalVariableInt(Thread thr, int depth, int slot, Object val) {
+    SetLocalVariableInt(thr, depth, slot, ((Number)val).intValue());
+  }
+  public static void SetLocalVariableLong(Thread thr, int depth, int slot, Object val) {
+    SetLocalVariableLong(thr, depth, slot, ((Number)val).longValue());
+  }
+  public static void SetLocalVariableFloat(Thread thr, int depth, int slot, Object val) {
+    SetLocalVariableFloat(thr, depth, slot, ((Number)val).floatValue());
+  }
+  public static void SetLocalVariableDouble(Thread thr, int depth, int slot, Object val) {
+    SetLocalVariableDouble(thr, depth, slot, ((Number)val).doubleValue());
+  }
+  public static native void SetLocalVariableInt(Thread thr, int depth, int slot, int val);
+  public static native void SetLocalVariableLong(Thread thr, int depth, int slot, long val);
+  public static native void SetLocalVariableFloat(Thread thr, int depth, int slot, float val);
+  public static native void SetLocalVariableDouble(Thread thr, int depth, int slot, double val);
+  public static native void SetLocalVariableObject(Thread thr, int depth, int slot, Object val);
+}
diff --git a/test/1911-get-local-var-table/src/art/Suspension.java b/test/1911-get-local-var-table/src/art/Suspension.java
new file mode 100644
index 0000000..16e62cc
--- /dev/null
+++ b/test/1911-get-local-var-table/src/art/Suspension.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package art;
+
+public class Suspension {
+  // Suspends a thread using jvmti.
+  public native static void suspend(Thread thr);
+
+  // Resumes a thread using jvmti.
+  public native static void resume(Thread thr);
+
+  public native static boolean isSuspended(Thread thr);
+
+  public native static int[] suspendList(Thread... threads);
+  public native static int[] resumeList(Thread... threads);
+}
diff --git a/test/1911-get-local-var-table/src/art/Test1911.java b/test/1911-get-local-var-table/src/art/Test1911.java
new file mode 100644
index 0000000..4dd9054
--- /dev/null
+++ b/test/1911-get-local-var-table/src/art/Test1911.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package art;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Executable;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.Base64;
+import java.util.HashSet;
+import java.util.Set;
+
+public class Test1911 {
+  // Class/dex file containing the following class.
+  //
+  // CLASS_BYTES generated with java version 1.8.0_45: javac -g art/Target.java
+  // DEX_BYTES generated with dx version 1.14: dx --dex --output=./classes.dex art/Target.class
+  //
+  // package art;
+  // import java.util.ArrayList;
+  // public class Target {
+  //   public int zzz;
+  //   public Target(int xxx) {
+  //     int q = xxx * 4;
+  //     zzz = q;
+  //   }
+  //   public static void doNothing(Object... objs) { doNothing(objs); }
+  //   public void doSomething(int x) {
+  //     doNothing(this);
+  //     int y = x + 3;
+  //     for (int z = 0; z < y * x; z++) {
+  //       float q = y - z;
+  //       double i = 0.3d * q;
+  //       doNothing(q, i);
+  //     }
+  //     Object o = new Object();
+  //     ArrayList<Integer> i = new ArrayList<>();
+  //     int p = 4 | x;
+  //     long q = 3 * p;
+  //     doNothing(p, q, o, i);
+  //   }
+  // }
+  public static byte[] CLASS_BYTES = Base64.getDecoder().decode(
+    "yv66vgAAADQARgoABAAuCQANAC8KAA0AMAcAMQY/0zMzMzMzMwoAMgAzCgA0ADUHADYKAAkALgoA" +
+    "NwA4CgA5ADoHADsBAAN6enoBAAFJAQAGPGluaXQ+AQAEKEkpVgEABENvZGUBAA9MaW5lTnVtYmVy" +
+    "VGFibGUBABJMb2NhbFZhcmlhYmxlVGFibGUBAAR0aGlzAQAMTGFydC9UYXJnZXQ7AQADeHh4AQAB" +
+    "cQEACWRvTm90aGluZwEAFihbTGphdmEvbGFuZy9PYmplY3Q7KVYBAARvYmpzAQATW0xqYXZhL2xh" +
+    "bmcvT2JqZWN0OwEAC2RvU29tZXRoaW5nAQABRgEAAWkBAAFEAQABegEAAXgBAAF5AQABbwEAEkxq" +
+    "YXZhL2xhbmcvT2JqZWN0OwEAFUxqYXZhL3V0aWwvQXJyYXlMaXN0OwEAAXABAAFKAQAWTG9jYWxW" +
+    "YXJpYWJsZVR5cGVUYWJsZQEAKkxqYXZhL3V0aWwvQXJyYXlMaXN0PExqYXZhL2xhbmcvSW50ZWdl" +
+    "cjs+OwEADVN0YWNrTWFwVGFibGUBAApTb3VyY2VGaWxlAQALVGFyZ2V0LmphdmEMABAAPAwADgAP" +
+    "DAAZABoBABBqYXZhL2xhbmcvT2JqZWN0BwA9DAA+AD8HAEAMAD4AQQEAE2phdmEvdXRpbC9BcnJh" +
+    "eUxpc3QHAEIMAD4AQwcARAwAPgBFAQAKYXJ0L1RhcmdldAEAAygpVgEAD2phdmEvbGFuZy9GbG9h" +
+    "dAEAB3ZhbHVlT2YBABQoRilMamF2YS9sYW5nL0Zsb2F0OwEAEGphdmEvbGFuZy9Eb3VibGUBABUo" +
+    "RClMamF2YS9sYW5nL0RvdWJsZTsBABFqYXZhL2xhbmcvSW50ZWdlcgEAFihJKUxqYXZhL2xhbmcv" +
+    "SW50ZWdlcjsBAA5qYXZhL2xhbmcvTG9uZwEAEyhKKUxqYXZhL2xhbmcvTG9uZzsAIQANAAQAAAAB" +
+    "AAEADgAPAAAAAwABABAAEQABABIAAABYAAIAAwAAAA4qtwABGwdoPSoctQACsQAAAAIAEwAAABIA" +
+    "BAAAAAUABAAGAAgABwANAAgAFAAAACAAAwAAAA4AFQAWAAAAAAAOABcADwABAAgABgAYAA8AAgCJ" +
+    "ABkAGgABABIAAAAvAAEAAQAAAAUquAADsQAAAAIAEwAAAAYAAQAAAAkAFAAAAAwAAQAAAAUAGwAc" +
+    "AAAAAQAdABEAAQASAAABWAAFAAgAAACCBL0ABFkDKlO4AAMbBmA9Az4dHBtoogAvHB1khjgEFAAF" +
+    "FwSNazkFBb0ABFkDFwS4AAdTWQQYBbgACFO4AAOEAwGn/9C7AARZtwABTrsACVm3AAo6BAcbgDYF" +
+    "BhUFaIU3Bge9AARZAxUFuAALU1kEFga4AAxTWQUtU1kGGQRTuAADsQAAAAQAEwAAADYADQAAAAsA" +
+    "CwAMAA8ADQAYAA4AHgAPACcAEAA+AA0ARAASAEwAEwBVABQAWgAVAGEAFgCBABcAFAAAAGYACgAe" +
+    "ACAAGAAeAAQAJwAXAB8AIAAFABEAMwAhAA8AAwAAAIIAFQAWAAAAAACCACIADwABAA8AcwAjAA8A" +
+    "AgBMADYAJAAlAAMAVQAtAB8AJgAEAFoAKAAnAA8ABQBhACEAGAAoAAYAKQAAAAwAAQBVAC0AHwAq" +
+    "AAQAKwAAAAoAAv0AEQEB+gAyAAEALAAAAAIALQ==");
+  public static byte[] DEX_BYTES = Base64.getDecoder().decode(
+    "ZGV4CjAzNQCQtgjEV631Ma/btYyIy2IzqHWNN+nZiwl0BQAAcAAAAHhWNBIAAAAAAAAAANQEAAAk" +
+    "AAAAcAAAAA0AAAAAAQAABwAAADQBAAABAAAAiAEAAAkAAACQAQAAAQAAANgBAAB8AwAA+AEAAB4D" +
+    "AAAmAwAAKQMAACwDAAAvAwAAMgMAADYDAAA6AwAAPgMAAEIDAABQAwAAZAMAAHcDAACMAwAAngMA" +
+    "ALIDAADJAwAA9QMAAAIEAAAFBAAACQQAAA0EAAAiBAAALQQAADoEAAA9BAAAQAQAAEYEAABJBAAA" +
+    "TAQAAFIEAABbBAAAXgQAAGMEAABmBAAAaQQAAAEAAAACAAAAAwAAAAQAAAAJAAAACgAAAAsAAAAM" +
+    "AAAADQAAAA4AAAAPAAAAEgAAABUAAAAFAAAABQAAAPgCAAAGAAAABgAAAAADAAAHAAAABwAAAAgD" +
+    "AAAIAAAACAAAABADAAASAAAACwAAAAAAAAATAAAACwAAAAgDAAAUAAAACwAAABgDAAAEAAIAIwAA" +
+    "AAQABQAAAAAABAAGABYAAAAEAAUAFwAAAAUAAAAeAAAABgABAB4AAAAHAAIAHgAAAAgAAwAeAAAA" +
+    "CQAEAAAAAAAKAAQAAAAAAAQAAAABAAAACQAAAAAAAAARAAAAAAAAAL4EAAAAAAAAAwACAAEAAABu" +
+    "BAAACAAAAHAQBwABANoAAgRZEAAADgABAAEAAQAAAHsEAAAEAAAAcRABAAAADgAQAAIAAgAAAIEE" +
+    "AABcAAAAEhkjmQwAEgpNDgkKcRABAAkA2AUPAxIIkgkFDzWYJACRCQUIgpYYCjMzMzMzM9M/iWyt" +
+    "AAoMEikjmQwAEgpxEAQABgAMC00LCQoSGnEgAwAQAAwLTQsJCnEQAQAJANgICAEo2yIDCQBwEAcA" +
+    "AwAiAgoAcBAIAAIA3gQPBNoJBAOBlhJJI5kMABIKcRAFAAQADAtNCwkKEhpxIAYAdgAMC00LCQoS" +
+    "Kk0DCQoSOk0CCQpxEAEACQAOAAEAAAAAAAAAAQAAAAEAAAABAAAAAgAAAAEAAAADAAAAAQAAAAwA" +
+    "Bjxpbml0PgABRAABRgABSQABSgACTEQAAkxGAAJMSQACTEoADExhcnQvVGFyZ2V0OwASTGphdmEv" +
+    "bGFuZy9Eb3VibGU7ABFMamF2YS9sYW5nL0Zsb2F0OwATTGphdmEvbGFuZy9JbnRlZ2VyOwAQTGph" +
+    "dmEvbGFuZy9Mb25nOwASTGphdmEvbGFuZy9PYmplY3Q7ABVMamF2YS91dGlsL0FycmF5TGlzdDsA" +
+    "KkxqYXZhL3V0aWwvQXJyYXlMaXN0PExqYXZhL2xhbmcvSW50ZWdlcjs+OwALVGFyZ2V0LmphdmEA" +
+    "AVYAAlZJAAJWTAATW0xqYXZhL2xhbmcvT2JqZWN0OwAJZG9Ob3RoaW5nAAtkb1NvbWV0aGluZwAB" +
+    "aQABbwAEb2JqcwABcAABcQAEdGhpcwAHdmFsdWVPZgABeAADeHh4AAF5AAF6AAN6enoABQEhBw48" +
+    "LQMAHQMtAAkBGwcOAAsBIAcOli0DBSIDAQEDCCMDSzwDBh0ChwMAGQEBFAtABQAFBloDAxoKWgQC" +
+    "GQsRLQMEHAM8AwYdBAEaDwAAAQIBAAEAgYAE+AMBiQGYBAIBsAQADQAAAAAAAAABAAAAAAAAAAEA" +
+    "AAAkAAAAcAAAAAIAAAANAAAAAAEAAAMAAAAHAAAANAEAAAQAAAABAAAAiAEAAAUAAAAJAAAAkAEA" +
+    "AAYAAAABAAAA2AEAAAEgAAADAAAA+AEAAAEQAAAFAAAA+AIAAAIgAAAkAAAAHgMAAAMgAAADAAAA" +
+    "bgQAAAAgAAABAAAAvgQAAAAQAAABAAAA1AQAAA==");
+
+
+  // The variables of the functions in the above Target class.
+  public static Set<Locals.VariableDescription>[] CONSTRUCTOR_VARIABLES = new Set[] {
+      // RI Local variable table
+      new HashSet<>(Arrays.asList(
+              new Locals.VariableDescription(8, 6, "q", "I", null, 2),
+              new Locals.VariableDescription(0, 14, "xxx", "I", null, 1),
+              new Locals.VariableDescription(0, 14, "this", "Lart/Target;", null, 0))),
+      // ART Local variable table
+      new HashSet<>(Arrays.asList(
+              new Locals.VariableDescription(0, 8, "this", "Lart/Target;", null, 1),
+              new Locals.VariableDescription(5, 3, "q", "I", null, 0),
+              new Locals.VariableDescription(0, 8, "xxx", "I", null, 2))),
+  };
+
+  public static Set<Locals.VariableDescription>[] DO_NOTHING_VARIABLES = new Set[] {
+      // RI Local variable table
+      new HashSet<>(Arrays.asList(
+              new Locals.VariableDescription(0, 5, "objs", "[Ljava/lang/Object;", null, 0))),
+      // ART Local variable table
+      new HashSet<>(Arrays.asList(
+              new Locals.VariableDescription(0, 4, "objs", "[Ljava/lang/Object;", null, 0))),
+  };
+
+  public static Set<Locals.VariableDescription>[] DO_SOMETHING_VARIABLES = new Set[] {
+      // RI Local variable table
+      new HashSet<>(Arrays.asList(
+              new Locals.VariableDescription(0, 130, "x", "I", null, 1),
+              new Locals.VariableDescription(76, 54, "o", "Ljava/lang/Object;", null, 3),
+              new Locals.VariableDescription(30, 32, "q", "F", null, 4),
+              new Locals.VariableDescription(39, 23, "i", "D", null, 5),
+              new Locals.VariableDescription(17, 51, "z", "I", null, 3),
+              new Locals.VariableDescription(15, 115, "y", "I", null, 2),
+              new Locals.VariableDescription(90, 40, "p", "I", null, 5),
+              new Locals.VariableDescription(97, 33, "q", "J", null, 6),
+              new Locals.VariableDescription(0, 130, "this", "Lart/Target;", null, 0),
+              new Locals.VariableDescription(85,
+                                             45,
+                                             "i",
+                                             "Ljava/util/ArrayList;",
+                                             "Ljava/util/ArrayList<Ljava/lang/Integer;>;",
+                                             4))),
+      // ART Local variable table
+      new HashSet<>(Arrays.asList(
+              new Locals.VariableDescription(19, 31, "q", "F", null, 6),
+              new Locals.VariableDescription(55, 37, "o", "Ljava/lang/Object;", null, 3),
+              new Locals.VariableDescription(0, 92, "this", "Lart/Target;", null, 14),
+              new Locals.VariableDescription(12, 80, "z", "I", null, 8),
+              new Locals.VariableDescription(11, 81, "y", "I", null, 5),
+              new Locals.VariableDescription(62, 30, "p", "I", null, 4),
+              new Locals.VariableDescription(0, 92, "x", "I", null, 15),
+              new Locals.VariableDescription(27, 23, "i", "D", null, 0),
+              new Locals.VariableDescription(65, 27, "q", "J", null, 6),
+              new Locals.VariableDescription(60,
+                                             32,
+                                             "i",
+                                             "Ljava/util/ArrayList;",
+                                             "Ljava/util/ArrayList<Ljava/lang/Integer;>;",
+                                             2))),
+  };
+
+  // Get a classloader that can load the Target class.
+  public static ClassLoader getClassLoader() throws Exception {
+    try {
+      Class<?> class_loader_class = Class.forName("dalvik.system.InMemoryDexClassLoader");
+      Constructor<?> ctor = class_loader_class.getConstructor(ByteBuffer.class, ClassLoader.class);
+      // We are on art since we got the InMemoryDexClassLoader.
+      return (ClassLoader)ctor.newInstance(
+          ByteBuffer.wrap(DEX_BYTES), Test1911.class.getClassLoader());
+    } catch (ClassNotFoundException e) {
+      // Running on RI.
+      return new ClassLoader(Test1911.class.getClassLoader()) {
+        protected Class<?> findClass(String name) throws ClassNotFoundException {
+          if (name.equals("art.Target")) {
+            return defineClass(name, CLASS_BYTES, 0, CLASS_BYTES.length);
+          } else {
+            return super.findClass(name);
+          }
+        }
+      };
+    }
+  }
+
+  public static void CheckLocalVariableTable(Executable m,
+          Set<Locals.VariableDescription>[] possible_vars) {
+    Set<Locals.VariableDescription> real_vars =
+            new HashSet<>(Arrays.asList(Locals.GetLocalVariableTable(m)));
+    for (Set<Locals.VariableDescription> pos : possible_vars) {
+      if (pos.equals(real_vars)) {
+        return;
+      }
+    }
+    System.out.println("Unexpected variables for " + m);
+    System.out.println("Received: " + real_vars);
+    System.out.println("Expected one of:");
+    for (Object pos : possible_vars) {
+      System.out.println("\t" + pos);
+    }
+  }
+  public static void run() throws Exception {
+    Locals.EnableLocalVariableAccess();
+    Class<?> target = getClassLoader().loadClass("art.Target");
+    CheckLocalVariableTable(target.getDeclaredConstructor(Integer.TYPE),
+            CONSTRUCTOR_VARIABLES);
+    CheckLocalVariableTable(target.getDeclaredMethod("doNothing", (new Object[0]).getClass()),
+            DO_NOTHING_VARIABLES);
+    CheckLocalVariableTable(target.getDeclaredMethod("doSomething", Integer.TYPE),
+            DO_SOMETHING_VARIABLES);
+  }
+}
+
diff --git a/test/1912-get-set-local-primitive/expected.txt b/test/1912-get-set-local-primitive/expected.txt
new file mode 100644
index 0000000..f2c5ce8
--- /dev/null
+++ b/test/1912-get-set-local-primitive/expected.txt
@@ -0,0 +1,108 @@
+Running public static void art.Test1912.IntMethod(java.lang.Runnable) with "GetInt" on remote thread.
+"GetInt" on public static void art.Test1912.IntMethod(java.lang.Runnable) got value: 42
+	Value is '42' (class: class java.lang.Integer)
+Running public static void art.Test1912.IntMethod(java.lang.Runnable) with "GetLong" on remote thread.
+"GetLong" on public static void art.Test1912.IntMethod(java.lang.Runnable) failed due to JVMTI_ERROR_TYPE_MISMATCH
+	Value is '42' (class: class java.lang.Integer)
+Running public static void art.Test1912.IntMethod(java.lang.Runnable) with "GetFloat" on remote thread.
+"GetFloat" on public static void art.Test1912.IntMethod(java.lang.Runnable) failed due to JVMTI_ERROR_TYPE_MISMATCH
+	Value is '42' (class: class java.lang.Integer)
+Running public static void art.Test1912.IntMethod(java.lang.Runnable) with "GetDouble" on remote thread.
+"GetDouble" on public static void art.Test1912.IntMethod(java.lang.Runnable) failed due to JVMTI_ERROR_TYPE_MISMATCH
+	Value is '42' (class: class java.lang.Integer)
+Running public static void art.Test1912.IntMethod(java.lang.Runnable) with "SetInt" on remote thread.
+"SetInt" on public static void art.Test1912.IntMethod(java.lang.Runnable) set value: 2147483647
+	Value is '2147483647' (class: class java.lang.Integer)
+Running public static void art.Test1912.IntMethod(java.lang.Runnable) with "SetLong" on remote thread.
+"SetLong" on public static void art.Test1912.IntMethod(java.lang.Runnable) failed to set value 9223372036854775807 due to JVMTI_ERROR_TYPE_MISMATCH
+	Value is '42' (class: class java.lang.Integer)
+Running public static void art.Test1912.IntMethod(java.lang.Runnable) with "SetFloat" on remote thread.
+"SetFloat" on public static void art.Test1912.IntMethod(java.lang.Runnable) failed to set value 9.2 due to JVMTI_ERROR_TYPE_MISMATCH
+	Value is '42' (class: class java.lang.Integer)
+Running public static void art.Test1912.IntMethod(java.lang.Runnable) with "SetDouble" on remote thread.
+"SetDouble" on public static void art.Test1912.IntMethod(java.lang.Runnable) failed to set value 12.4 due to JVMTI_ERROR_TYPE_MISMATCH
+	Value is '42' (class: class java.lang.Integer)
+Running public static void art.Test1912.LongMethod(java.lang.Runnable) with "GetInt" on remote thread.
+"GetInt" on public static void art.Test1912.LongMethod(java.lang.Runnable) failed due to JVMTI_ERROR_TYPE_MISMATCH
+	Value is '9001' (class: class java.lang.Long)
+Running public static void art.Test1912.LongMethod(java.lang.Runnable) with "GetLong" on remote thread.
+"GetLong" on public static void art.Test1912.LongMethod(java.lang.Runnable) got value: 9001
+	Value is '9001' (class: class java.lang.Long)
+Running public static void art.Test1912.LongMethod(java.lang.Runnable) with "GetFloat" on remote thread.
+"GetFloat" on public static void art.Test1912.LongMethod(java.lang.Runnable) failed due to JVMTI_ERROR_TYPE_MISMATCH
+	Value is '9001' (class: class java.lang.Long)
+Running public static void art.Test1912.LongMethod(java.lang.Runnable) with "GetDouble" on remote thread.
+"GetDouble" on public static void art.Test1912.LongMethod(java.lang.Runnable) failed due to JVMTI_ERROR_TYPE_MISMATCH
+	Value is '9001' (class: class java.lang.Long)
+Running public static void art.Test1912.LongMethod(java.lang.Runnable) with "SetInt" on remote thread.
+"SetInt" on public static void art.Test1912.LongMethod(java.lang.Runnable) failed to set value 2147483647 due to JVMTI_ERROR_TYPE_MISMATCH
+	Value is '9001' (class: class java.lang.Long)
+Running public static void art.Test1912.LongMethod(java.lang.Runnable) with "SetLong" on remote thread.
+"SetLong" on public static void art.Test1912.LongMethod(java.lang.Runnable) set value: 9223372036854775807
+	Value is '9223372036854775807' (class: class java.lang.Long)
+Running public static void art.Test1912.LongMethod(java.lang.Runnable) with "SetFloat" on remote thread.
+"SetFloat" on public static void art.Test1912.LongMethod(java.lang.Runnable) failed to set value 9.2 due to JVMTI_ERROR_TYPE_MISMATCH
+	Value is '9001' (class: class java.lang.Long)
+Running public static void art.Test1912.LongMethod(java.lang.Runnable) with "SetDouble" on remote thread.
+"SetDouble" on public static void art.Test1912.LongMethod(java.lang.Runnable) failed to set value 12.4 due to JVMTI_ERROR_TYPE_MISMATCH
+	Value is '9001' (class: class java.lang.Long)
+Running public static void art.Test1912.FloatMethod(java.lang.Runnable) with "GetInt" on remote thread.
+"GetInt" on public static void art.Test1912.FloatMethod(java.lang.Runnable) failed due to JVMTI_ERROR_TYPE_MISMATCH
+	Value is '1.618' (class: class java.lang.Float)
+Running public static void art.Test1912.FloatMethod(java.lang.Runnable) with "GetLong" on remote thread.
+"GetLong" on public static void art.Test1912.FloatMethod(java.lang.Runnable) failed due to JVMTI_ERROR_TYPE_MISMATCH
+	Value is '1.618' (class: class java.lang.Float)
+Running public static void art.Test1912.FloatMethod(java.lang.Runnable) with "GetFloat" on remote thread.
+"GetFloat" on public static void art.Test1912.FloatMethod(java.lang.Runnable) got value: 1.618
+	Value is '1.618' (class: class java.lang.Float)
+Running public static void art.Test1912.FloatMethod(java.lang.Runnable) with "GetDouble" on remote thread.
+"GetDouble" on public static void art.Test1912.FloatMethod(java.lang.Runnable) failed due to JVMTI_ERROR_TYPE_MISMATCH
+	Value is '1.618' (class: class java.lang.Float)
+Running public static void art.Test1912.FloatMethod(java.lang.Runnable) with "SetInt" on remote thread.
+"SetInt" on public static void art.Test1912.FloatMethod(java.lang.Runnable) failed to set value 2147483647 due to JVMTI_ERROR_TYPE_MISMATCH
+	Value is '1.618' (class: class java.lang.Float)
+Running public static void art.Test1912.FloatMethod(java.lang.Runnable) with "SetLong" on remote thread.
+"SetLong" on public static void art.Test1912.FloatMethod(java.lang.Runnable) failed to set value 9223372036854775807 due to JVMTI_ERROR_TYPE_MISMATCH
+	Value is '1.618' (class: class java.lang.Float)
+Running public static void art.Test1912.FloatMethod(java.lang.Runnable) with "SetFloat" on remote thread.
+"SetFloat" on public static void art.Test1912.FloatMethod(java.lang.Runnable) set value: 9.2
+	Value is '9.2' (class: class java.lang.Float)
+Running public static void art.Test1912.FloatMethod(java.lang.Runnable) with "SetDouble" on remote thread.
+"SetDouble" on public static void art.Test1912.FloatMethod(java.lang.Runnable) failed to set value 12.4 due to JVMTI_ERROR_TYPE_MISMATCH
+	Value is '1.618' (class: class java.lang.Float)
+Running public static void art.Test1912.DoubleMethod(java.lang.Runnable) with "GetInt" on remote thread.
+"GetInt" on public static void art.Test1912.DoubleMethod(java.lang.Runnable) failed due to JVMTI_ERROR_TYPE_MISMATCH
+	Value is '3.1415' (class: class java.lang.Double)
+Running public static void art.Test1912.DoubleMethod(java.lang.Runnable) with "GetLong" on remote thread.
+"GetLong" on public static void art.Test1912.DoubleMethod(java.lang.Runnable) failed due to JVMTI_ERROR_TYPE_MISMATCH
+	Value is '3.1415' (class: class java.lang.Double)
+Running public static void art.Test1912.DoubleMethod(java.lang.Runnable) with "GetFloat" on remote thread.
+"GetFloat" on public static void art.Test1912.DoubleMethod(java.lang.Runnable) failed due to JVMTI_ERROR_TYPE_MISMATCH
+	Value is '3.1415' (class: class java.lang.Double)
+Running public static void art.Test1912.DoubleMethod(java.lang.Runnable) with "GetDouble" on remote thread.
+"GetDouble" on public static void art.Test1912.DoubleMethod(java.lang.Runnable) got value: 3.1415
+	Value is '3.1415' (class: class java.lang.Double)
+Running public static void art.Test1912.DoubleMethod(java.lang.Runnable) with "SetInt" on remote thread.
+"SetInt" on public static void art.Test1912.DoubleMethod(java.lang.Runnable) failed to set value 2147483647 due to JVMTI_ERROR_TYPE_MISMATCH
+	Value is '3.1415' (class: class java.lang.Double)
+Running public static void art.Test1912.DoubleMethod(java.lang.Runnable) with "SetLong" on remote thread.
+"SetLong" on public static void art.Test1912.DoubleMethod(java.lang.Runnable) failed to set value 9223372036854775807 due to JVMTI_ERROR_TYPE_MISMATCH
+	Value is '3.1415' (class: class java.lang.Double)
+Running public static void art.Test1912.DoubleMethod(java.lang.Runnable) with "SetFloat" on remote thread.
+"SetFloat" on public static void art.Test1912.DoubleMethod(java.lang.Runnable) failed to set value 9.2 due to JVMTI_ERROR_TYPE_MISMATCH
+	Value is '3.1415' (class: class java.lang.Double)
+Running public static void art.Test1912.DoubleMethod(java.lang.Runnable) with "SetDouble" on remote thread.
+"SetDouble" on public static void art.Test1912.DoubleMethod(java.lang.Runnable) set value: 12.4
+	Value is '12.4' (class: class java.lang.Double)
+Running public static void art.Test1912.BooleanMethod(java.lang.Runnable) with "SetIntBoolSize" on remote thread.
+"SetIntBoolSize" on public static void art.Test1912.BooleanMethod(java.lang.Runnable) set value: 1
+	Value is 'true' (class: class java.lang.Boolean)
+Running public static void art.Test1912.ByteMethod(java.lang.Runnable) with "SetIntByteSize" on remote thread.
+"SetIntByteSize" on public static void art.Test1912.ByteMethod(java.lang.Runnable) set value: 126
+	Value is '126' (class: class java.lang.Byte)
+Running public static void art.Test1912.CharMethod(java.lang.Runnable) with "SetIntCharSize" on remote thread.
+"SetIntCharSize" on public static void art.Test1912.CharMethod(java.lang.Runnable) set value: 65534
+	Value is '<Char: -1>' (class: class java.lang.String)
+Running public static void art.Test1912.ShortMethod(java.lang.Runnable) with "SetIntShortSize" on remote thread.
+"SetIntShortSize" on public static void art.Test1912.ShortMethod(java.lang.Runnable) set value: 32766
+	Value is '32766' (class: class java.lang.Short)
diff --git a/test/1912-get-set-local-primitive/info.txt b/test/1912-get-set-local-primitive/info.txt
new file mode 100644
index 0000000..87a7b35
--- /dev/null
+++ b/test/1912-get-set-local-primitive/info.txt
@@ -0,0 +1,2 @@
+Tests for jvmti get/set Local variable primitives.
+
diff --git a/test/1912-get-set-local-primitive/run b/test/1912-get-set-local-primitive/run
new file mode 100755
index 0000000..51875a7
--- /dev/null
+++ b/test/1912-get-set-local-primitive/run
@@ -0,0 +1,18 @@
+#!/bin/bash
+#
+# Copyright 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Ask for stack traces to be dumped to a file rather than to stdout.
+./default-run "$@" --jvmti
diff --git a/test/1912-get-set-local-primitive/src/Main.java b/test/1912-get-set-local-primitive/src/Main.java
new file mode 100644
index 0000000..9ff69f8
--- /dev/null
+++ b/test/1912-get-set-local-primitive/src/Main.java
@@ -0,0 +1,21 @@
+/*
+ * 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 {
+    art.Test1912.run();
+  }
+}
diff --git a/test/1912-get-set-local-primitive/src/art/Breakpoint.java b/test/1912-get-set-local-primitive/src/art/Breakpoint.java
new file mode 100644
index 0000000..bbb89f7
--- /dev/null
+++ b/test/1912-get-set-local-primitive/src/art/Breakpoint.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package art;
+
+import java.lang.reflect.Executable;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.Objects;
+
+public class Breakpoint {
+  public static class Manager {
+    public static class BP {
+      public final Executable method;
+      public final long location;
+
+      public BP(Executable method) {
+        this(method, getStartLocation(method));
+      }
+
+      public BP(Executable method, long location) {
+        this.method = method;
+        this.location = location;
+      }
+
+      @Override
+      public boolean equals(Object other) {
+        return (other instanceof BP) &&
+            method.equals(((BP)other).method) &&
+            location == ((BP)other).location;
+      }
+
+      @Override
+      public String toString() {
+        return method.toString() + " @ " + getLine();
+      }
+
+      @Override
+      public int hashCode() {
+        return Objects.hash(method, location);
+      }
+
+      public int getLine() {
+        try {
+          LineNumber[] lines = getLineNumberTable(method);
+          int best = -1;
+          for (LineNumber l : lines) {
+            if (l.location > location) {
+              break;
+            } else {
+              best = l.line;
+            }
+          }
+          return best;
+        } catch (Exception e) {
+          return -1;
+        }
+      }
+    }
+
+    private Set<BP> breaks = new HashSet<>();
+
+    public void setBreakpoints(BP... bs) {
+      for (BP b : bs) {
+        if (breaks.add(b)) {
+          Breakpoint.setBreakpoint(b.method, b.location);
+        }
+      }
+    }
+    public void setBreakpoint(Executable method, long location) {
+      setBreakpoints(new BP(method, location));
+    }
+
+    public void clearBreakpoints(BP... bs) {
+      for (BP b : bs) {
+        if (breaks.remove(b)) {
+          Breakpoint.clearBreakpoint(b.method, b.location);
+        }
+      }
+    }
+    public void clearBreakpoint(Executable method, long location) {
+      clearBreakpoints(new BP(method, location));
+    }
+
+    public void clearAllBreakpoints() {
+      clearBreakpoints(breaks.toArray(new BP[0]));
+    }
+  }
+
+  public static void startBreakpointWatch(Class<?> methodClass,
+                                          Executable breakpointReached,
+                                          Thread thr) {
+    startBreakpointWatch(methodClass, breakpointReached, false, thr);
+  }
+
+  /**
+   * Enables the trapping of breakpoint events.
+   *
+   * If allowRecursive == true then breakpoints will be sent even if one is currently being handled.
+   */
+  public static native void startBreakpointWatch(Class<?> methodClass,
+                                                 Executable breakpointReached,
+                                                 boolean allowRecursive,
+                                                 Thread thr);
+  public static native void stopBreakpointWatch(Thread thr);
+
+  public static final class LineNumber implements Comparable<LineNumber> {
+    public final long location;
+    public final int line;
+
+    private LineNumber(long loc, int line) {
+      this.location = loc;
+      this.line = line;
+    }
+
+    public boolean equals(Object other) {
+      return other instanceof LineNumber && ((LineNumber)other).line == line &&
+          ((LineNumber)other).location == location;
+    }
+
+    public int compareTo(LineNumber other) {
+      int v = Integer.valueOf(line).compareTo(Integer.valueOf(other.line));
+      if (v != 0) {
+        return v;
+      } else {
+        return Long.valueOf(location).compareTo(Long.valueOf(other.location));
+      }
+    }
+  }
+
+  public static native void setBreakpoint(Executable m, long loc);
+  public static void setBreakpoint(Executable m, LineNumber l) {
+    setBreakpoint(m, l.location);
+  }
+
+  public static native void clearBreakpoint(Executable m, long loc);
+  public static void clearBreakpoint(Executable m, LineNumber l) {
+    clearBreakpoint(m, l.location);
+  }
+
+  private static native Object[] getLineNumberTableNative(Executable m);
+  public static LineNumber[] getLineNumberTable(Executable m) {
+    Object[] nativeTable = getLineNumberTableNative(m);
+    long[] location = (long[])(nativeTable[0]);
+    int[] lines = (int[])(nativeTable[1]);
+    if (lines.length != location.length) {
+      throw new Error("Lines and locations have different lengths!");
+    }
+    LineNumber[] out = new LineNumber[lines.length];
+    for (int i = 0; i < lines.length; i++) {
+      out[i] = new LineNumber(location[i], lines[i]);
+    }
+    return out;
+  }
+
+  public static native long getStartLocation(Executable m);
+
+  public static int locationToLine(Executable m, long location) {
+    try {
+      Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m);
+      int best = -1;
+      for (Breakpoint.LineNumber l : lines) {
+        if (l.location > location) {
+          break;
+        } else {
+          best = l.line;
+        }
+      }
+      return best;
+    } catch (Exception e) {
+      return -1;
+    }
+  }
+
+  public static long lineToLocation(Executable m, int line) throws Exception {
+    try {
+      Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m);
+      for (Breakpoint.LineNumber l : lines) {
+        if (l.line == line) {
+          return l.location;
+        }
+      }
+      throw new Exception("Unable to find line " + line + " in " + m);
+    } catch (Exception e) {
+      throw new Exception("Unable to get line number info for " + m, e);
+    }
+  }
+}
+
diff --git a/test/1912-get-set-local-primitive/src/art/Locals.java b/test/1912-get-set-local-primitive/src/art/Locals.java
new file mode 100644
index 0000000..22e21be
--- /dev/null
+++ b/test/1912-get-set-local-primitive/src/art/Locals.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package art;
+
+import java.lang.reflect.Executable;
+import java.util.Objects;
+
+public class Locals {
+  public static native void EnableLocalVariableAccess();
+
+  public static class VariableDescription {
+    public final long start_location;
+    public final int length;
+    public final String name;
+    public final String signature;
+    public final String generic_signature;
+    public final int slot;
+
+    public VariableDescription(
+        long start, int length, String name, String sig, String gen_sig, int slot) {
+      this.start_location = start;
+      this.length = length;
+      this.name = name;
+      this.signature = sig;
+      this.generic_signature = gen_sig;
+      this.slot = slot;
+    }
+
+    @Override
+    public String toString() {
+      return String.format(
+          "VariableDescription { " +
+            "Sig: '%s', Name: '%s', Gen_sig: '%s', slot: %d, start: %d, len: %d" +
+          "}",
+          this.signature,
+          this.name,
+          this.generic_signature,
+          this.slot,
+          this.start_location,
+          this.length);
+    }
+    public boolean equals(Object other) {
+      if (!(other instanceof VariableDescription)) {
+        return false;
+      } else {
+        VariableDescription v = (VariableDescription)other;
+        return Objects.equals(v.signature, signature) &&
+            Objects.equals(v.name, name) &&
+            Objects.equals(v.generic_signature, generic_signature) &&
+            v.slot == slot &&
+            v.start_location == start_location &&
+            v.length == length;
+      }
+    }
+    public int hashCode() {
+      return Objects.hash(this.signature, this.name, this.generic_signature, this.slot,
+          this.start_location, this.length);
+    }
+  }
+
+  public static native VariableDescription[] GetLocalVariableTable(Executable e);
+
+  public static VariableDescription GetVariableAtLine(
+      Executable e, String name, String sig, int line) throws Exception {
+    return GetVariableAtLocation(e, name, sig, Breakpoint.lineToLocation(e, line));
+  }
+
+  public static VariableDescription GetVariableAtLocation(
+      Executable e, String name, String sig, long loc) {
+    VariableDescription[] vars = GetLocalVariableTable(e);
+    for (VariableDescription var : vars) {
+      if (var.start_location <= loc &&
+          var.length + var.start_location > loc &&
+          var.name.equals(name) &&
+          var.signature.equals(sig)) {
+        return var;
+      }
+    }
+    throw new Error(
+        "Unable to find variable " + name + " (sig: " + sig + ") in " + e + " at loc " + loc);
+  }
+
+  public static native int GetLocalVariableInt(Thread thr, int depth, int slot);
+  public static native long GetLocalVariableLong(Thread thr, int depth, int slot);
+  public static native float GetLocalVariableFloat(Thread thr, int depth, int slot);
+  public static native double GetLocalVariableDouble(Thread thr, int depth, int slot);
+  public static native Object GetLocalVariableObject(Thread thr, int depth, int slot);
+  public static native Object GetLocalInstance(Thread thr, int depth);
+
+  public static void SetLocalVariableInt(Thread thr, int depth, int slot, Object val) {
+    SetLocalVariableInt(thr, depth, slot, ((Number)val).intValue());
+  }
+  public static void SetLocalVariableLong(Thread thr, int depth, int slot, Object val) {
+    SetLocalVariableLong(thr, depth, slot, ((Number)val).longValue());
+  }
+  public static void SetLocalVariableFloat(Thread thr, int depth, int slot, Object val) {
+    SetLocalVariableFloat(thr, depth, slot, ((Number)val).floatValue());
+  }
+  public static void SetLocalVariableDouble(Thread thr, int depth, int slot, Object val) {
+    SetLocalVariableDouble(thr, depth, slot, ((Number)val).doubleValue());
+  }
+  public static native void SetLocalVariableInt(Thread thr, int depth, int slot, int val);
+  public static native void SetLocalVariableLong(Thread thr, int depth, int slot, long val);
+  public static native void SetLocalVariableFloat(Thread thr, int depth, int slot, float val);
+  public static native void SetLocalVariableDouble(Thread thr, int depth, int slot, double val);
+  public static native void SetLocalVariableObject(Thread thr, int depth, int slot, Object val);
+}
diff --git a/test/1912-get-set-local-primitive/src/art/StackTrace.java b/test/1912-get-set-local-primitive/src/art/StackTrace.java
new file mode 100644
index 0000000..2ea2f20
--- /dev/null
+++ b/test/1912-get-set-local-primitive/src/art/StackTrace.java
@@ -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.
+ */
+
+package art;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Executable;
+
+public class StackTrace {
+  public static class StackFrameData {
+    public final Thread thr;
+    public final Executable method;
+    public final long current_location;
+    public final int depth;
+
+    public StackFrameData(Thread thr, Executable e, long loc, int depth) {
+      this.thr = thr;
+      this.method = e;
+      this.current_location = loc;
+      this.depth = depth;
+    }
+    @Override
+    public String toString() {
+      return String.format(
+          "StackFrameData { thr: '%s', method: '%s', loc: %d, depth: %d }",
+          this.thr,
+          this.method,
+          this.current_location,
+          this.depth);
+    }
+  }
+
+  public static native int GetStackDepth(Thread thr);
+
+  private static native StackFrameData[] nativeGetStackTrace(Thread thr);
+
+  public static StackFrameData[] GetStackTrace(Thread thr) {
+    // The RI seems to give inconsistent (and sometimes nonsensical) results if the thread is not
+    // suspended. The spec says that not being suspended is fine but since we want this to be
+    // consistent we will suspend for the RI.
+    boolean suspend_thread =
+        !System.getProperty("java.vm.name").equals("Dalvik") &&
+        !thr.equals(Thread.currentThread()) &&
+        !Suspension.isSuspended(thr);
+    if (suspend_thread) {
+      Suspension.suspend(thr);
+    }
+    StackFrameData[] out = nativeGetStackTrace(thr);
+    if (suspend_thread) {
+      Suspension.resume(thr);
+    }
+    return out;
+  }
+}
+
diff --git a/test/1912-get-set-local-primitive/src/art/Suspension.java b/test/1912-get-set-local-primitive/src/art/Suspension.java
new file mode 100644
index 0000000..16e62cc
--- /dev/null
+++ b/test/1912-get-set-local-primitive/src/art/Suspension.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package art;
+
+public class Suspension {
+  // Suspends a thread using jvmti.
+  public native static void suspend(Thread thr);
+
+  // Resumes a thread using jvmti.
+  public native static void resume(Thread thr);
+
+  public native static boolean isSuspended(Thread thr);
+
+  public native static int[] suspendList(Thread... threads);
+  public native static int[] resumeList(Thread... threads);
+}
diff --git a/test/1912-get-set-local-primitive/src/art/Test1912.java b/test/1912-get-set-local-primitive/src/art/Test1912.java
new file mode 100644
index 0000000..24149f4
--- /dev/null
+++ b/test/1912-get-set-local-primitive/src/art/Test1912.java
@@ -0,0 +1,260 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package art;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Executable;
+import java.lang.reflect.Method;
+import java.nio.ByteBuffer;
+import java.util.concurrent.Semaphore;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+import java.util.function.Consumer;
+
+// TODO Rename test to set-get-local-prim
+
+public class Test1912 {
+  public static final String TARGET_VAR = "TARGET";
+
+
+  public static void reportValue(Object val) {
+    if (val instanceof Character) {
+      val = "<Char: " + Character.getNumericValue(((Character)val).charValue()) + ">";
+    }
+    System.out.println("\tValue is '" + val + "' (class: " + val.getClass() + ")");
+  }
+
+  public static void BooleanMethod(Runnable safepoint) {
+    boolean TARGET = false;
+    safepoint.run();
+    reportValue(TARGET);
+  }
+  public static void ByteMethod(Runnable safepoint) {
+    byte TARGET = 8;
+    safepoint.run();
+    reportValue(TARGET);
+  }
+  public static void CharMethod(Runnable safepoint) {
+    char TARGET = 'q';
+    safepoint.run();
+    reportValue(TARGET);
+  }
+  public static void ShortMethod(Runnable safepoint) {
+    short TARGET = 321;
+    safepoint.run();
+    reportValue(TARGET);
+  }
+  public static void IntMethod(Runnable safepoint) {
+    int TARGET = 42;
+    safepoint.run();
+    reportValue(TARGET);
+  }
+  public static void LongMethod(Runnable safepoint) {
+    long TARGET = 9001;
+    safepoint.run();
+    reportValue(TARGET);
+  }
+  public static void FloatMethod(Runnable safepoint) {
+    float TARGET = 1.618f;
+    safepoint.run();
+    reportValue(TARGET);
+  }
+  public static void DoubleMethod(Runnable safepoint) {
+    double TARGET = 3.1415d;
+    safepoint.run();
+    reportValue(TARGET);
+  }
+
+  public static interface SafepointFunction {
+    public void invoke(
+        Thread thread,
+        Method target,
+        Locals.VariableDescription TARGET_desc,
+        int depth) throws Exception;
+  }
+
+  public static interface SetterFunction {
+    public void SetVar(Thread t, int depth, int slot, Object v);
+  }
+
+  public static interface GetterFunction {
+    public Object GetVar(Thread t, int depth, int slot);
+  }
+
+  public static SafepointFunction NamedSet(
+      final String type, final SetterFunction get, final Object v) {
+    return new SafepointFunction() {
+      public void invoke(Thread t, Method method, Locals.VariableDescription desc, int depth) {
+        try {
+          get.SetVar(t, depth, desc.slot, v);
+          System.out.println(this + " on " + method + " set value: " + v);
+        } catch (Exception e) {
+          System.out.println(
+              this + " on " + method + " failed to set value " + v + " due to " + e.getMessage());
+        }
+      }
+      public String toString() {
+        return "\"Set" + type + "\"";
+      }
+    };
+  }
+
+  public static SafepointFunction NamedGet(final String type, final GetterFunction get) {
+    return new SafepointFunction() {
+      public void invoke(Thread t, Method method, Locals.VariableDescription desc, int depth) {
+        try {
+          Object res = get.GetVar(t, depth, desc.slot);
+          System.out.println(this + " on " + method + " got value: " + res);
+        } catch (Exception e) {
+          System.out.println(this + " on " + method + " failed due to " + e.getMessage());
+        }
+      }
+      public String toString() {
+        return "\"Get" + type + "\"";
+      }
+    };
+  }
+
+  public static class TestCase {
+    public final Method target;
+
+    public TestCase(Method target) {
+      this.target = target;
+    }
+
+    public static class ThreadPauser implements Runnable {
+      public final Semaphore sem_wakeup_main;
+      public final Semaphore sem_wait;
+
+      public ThreadPauser() {
+        sem_wakeup_main = new Semaphore(0);
+        sem_wait = new Semaphore(0);
+      }
+
+      public void run() {
+        try {
+          sem_wakeup_main.release();
+          sem_wait.acquire();
+        } catch (Exception e) {
+          throw new Error("Error with semaphores!", e);
+        }
+      }
+
+      public void waitForOtherThreadToPause() throws Exception {
+        sem_wakeup_main.acquire();
+      }
+
+      public void wakeupOtherThread() throws Exception {
+        sem_wait.release();
+      }
+    }
+
+    public void exec(final SafepointFunction safepoint) throws Exception {
+      System.out.println("Running " + target + " with " + safepoint + " on remote thread.");
+      final ThreadPauser pause = new ThreadPauser();
+      Thread remote = new Thread(
+          () -> {
+            try {
+              target.invoke(null, pause);
+            } catch (Exception e) {
+              throw new Error("Error invoking remote thread " + Thread.currentThread(), e);
+            }
+          },
+          "remote thread for " + target + " with " + safepoint);
+      remote.start();
+      pause.waitForOtherThreadToPause();
+      try {
+        Suspension.suspend(remote);
+        StackTrace.StackFrameData frame = findStackFrame(remote);
+        Locals.VariableDescription desc = findTargetVar(frame.current_location);
+        safepoint.invoke(remote, target, desc, frame.depth);
+      } finally {
+        Suspension.resume(remote);
+        pause.wakeupOtherThread();
+        remote.join();
+      }
+    }
+
+    private Locals.VariableDescription findTargetVar(long loc) {
+      for (Locals.VariableDescription var : Locals.GetLocalVariableTable(target)) {
+        if (var.start_location <= loc &&
+            var.length + var.start_location > loc &&
+            var.name.equals(TARGET_VAR)) {
+          return var;
+        }
+      }
+      throw new Error(
+          "Unable to find variable " + TARGET_VAR + " in " + target + " at loc " + loc);
+    }
+
+    private StackTrace.StackFrameData findStackFrame(Thread thr) {
+      for (StackTrace.StackFrameData frame : StackTrace.GetStackTrace(thr)) {
+        if (frame.method.equals(target)) {
+          return frame;
+        }
+      }
+      throw new Error("Unable to find stack frame in method " + target + " on thread " + thr);
+    }
+  }
+  public static Method getMethod(String name) throws Exception {
+    return Test1912.class.getDeclaredMethod(name, Runnable.class);
+  }
+
+  public static void run() throws Exception {
+    Locals.EnableLocalVariableAccess();
+    final TestCase[] MAIN_TEST_CASES = new TestCase[] {
+      new TestCase(getMethod("IntMethod")),
+      new TestCase(getMethod("LongMethod")),
+      new TestCase(getMethod("FloatMethod")),
+      new TestCase(getMethod("DoubleMethod")),
+    };
+
+    final SafepointFunction[] SAFEPOINTS = new SafepointFunction[] {
+      NamedGet("Int", Locals::GetLocalVariableInt),
+      NamedGet("Long", Locals::GetLocalVariableLong),
+      NamedGet("Float", Locals::GetLocalVariableFloat),
+      NamedGet("Double", Locals::GetLocalVariableDouble),
+      NamedSet("Int", Locals::SetLocalVariableInt, Integer.MAX_VALUE),
+      NamedSet("Long", Locals::SetLocalVariableLong, Long.MAX_VALUE),
+      NamedSet("Float", Locals::SetLocalVariableFloat, 9.2f),
+      NamedSet("Double", Locals::SetLocalVariableDouble, 12.4d),
+    };
+
+    for (TestCase t: MAIN_TEST_CASES) {
+      for (SafepointFunction s : SAFEPOINTS) {
+        t.exec(s);
+      }
+    }
+
+    // Test int for small values.
+    new TestCase(getMethod("BooleanMethod")).exec(
+        NamedSet("IntBoolSize", Locals::SetLocalVariableInt, 1));
+    new TestCase(getMethod("ByteMethod")).exec(
+      NamedSet("IntByteSize", Locals::SetLocalVariableInt, Byte.MAX_VALUE - 1));
+
+    new TestCase(getMethod("CharMethod")).exec(
+      NamedSet("IntCharSize", Locals::SetLocalVariableInt, Character.MAX_VALUE - 1));
+    new TestCase(getMethod("ShortMethod")).exec(
+      NamedSet("IntShortSize", Locals::SetLocalVariableInt, Short.MAX_VALUE - 1));
+  }
+}
+
diff --git a/test/1913-get-set-local-objects/expected.txt b/test/1913-get-set-local-objects/expected.txt
new file mode 100644
index 0000000..23f4992
--- /dev/null
+++ b/test/1913-get-set-local-objects/expected.txt
@@ -0,0 +1,72 @@
+Running public static void art.Test1913.ObjectMethod(java.lang.Runnable) with "GetGetObject" on remote thread.
+"GetGetObject" on public static void art.Test1913.ObjectMethod(java.lang.Runnable) got value: TestClass1("ObjectMethod")
+	Value is 'TestClass1("ObjectMethod")' (class: class art.Test1913$TestClass1)
+Running public static void art.Test1913.ObjectMethod(java.lang.Runnable) with "SetNull" on remote thread.
+"SetNull" on public static void art.Test1913.ObjectMethod(java.lang.Runnable) set value: null
+	Value is 'null' (class: NULL)
+Running public static void art.Test1913.ObjectMethod(java.lang.Runnable) with "SetTestClass1" on remote thread.
+"SetTestClass1" on public static void art.Test1913.ObjectMethod(java.lang.Runnable) set value: TestClass1("Set TestClass1")
+	Value is 'TestClass1("Set TestClass1")' (class: class art.Test1913$TestClass1)
+Running public static void art.Test1913.ObjectMethod(java.lang.Runnable) with "SetTestClass1ext" on remote thread.
+"SetTestClass1ext" on public static void art.Test1913.ObjectMethod(java.lang.Runnable) set value: TestClass1ext("TestClass1("Set TestClass1ext")")
+	Value is 'TestClass1ext("TestClass1("Set TestClass1ext")")' (class: class art.Test1913$TestClass1ext)
+Running public static void art.Test1913.ObjectMethod(java.lang.Runnable) with "SetTestClass2" on remote thread.
+"SetTestClass2" on public static void art.Test1913.ObjectMethod(java.lang.Runnable) set value: TestClass2("Set TestClass2")
+	Value is 'TestClass2("Set TestClass2")' (class: class art.Test1913$TestClass2)
+Running public static void art.Test1913.ObjectMethod(java.lang.Runnable) with "SetTestClass2impl" on remote thread.
+"SetTestClass2impl" on public static void art.Test1913.ObjectMethod(java.lang.Runnable) set value: TestClass2impl("TestClass2("Set TestClass2impl")")
+	Value is 'TestClass2impl("TestClass2("Set TestClass2impl")")' (class: class art.Test1913$TestClass2impl)
+Running public static void art.Test1913.InterfaceMethod(java.lang.Runnable) with "GetGetObject" on remote thread.
+"GetGetObject" on public static void art.Test1913.InterfaceMethod(java.lang.Runnable) got value: TestClass1("InterfaceMethod")
+	Value is 'TestClass1("InterfaceMethod")' (class: class art.Test1913$TestClass1)
+Running public static void art.Test1913.InterfaceMethod(java.lang.Runnable) with "SetNull" on remote thread.
+"SetNull" on public static void art.Test1913.InterfaceMethod(java.lang.Runnable) set value: null
+	Value is 'null' (class: NULL)
+Running public static void art.Test1913.InterfaceMethod(java.lang.Runnable) with "SetTestClass1" on remote thread.
+"SetTestClass1" on public static void art.Test1913.InterfaceMethod(java.lang.Runnable) set value: TestClass1("Set TestClass1")
+	Value is 'TestClass1("Set TestClass1")' (class: class art.Test1913$TestClass1)
+Running public static void art.Test1913.InterfaceMethod(java.lang.Runnable) with "SetTestClass1ext" on remote thread.
+"SetTestClass1ext" on public static void art.Test1913.InterfaceMethod(java.lang.Runnable) set value: TestClass1ext("TestClass1("Set TestClass1ext")")
+	Value is 'TestClass1ext("TestClass1("Set TestClass1ext")")' (class: class art.Test1913$TestClass1ext)
+Running public static void art.Test1913.InterfaceMethod(java.lang.Runnable) with "SetTestClass2" on remote thread.
+"SetTestClass2" on public static void art.Test1913.InterfaceMethod(java.lang.Runnable) failed to set value TestClass2("Set TestClass2") due to JVMTI_ERROR_TYPE_MISMATCH
+	Value is 'TestClass1("InterfaceMethod")' (class: class art.Test1913$TestClass1)
+Running public static void art.Test1913.InterfaceMethod(java.lang.Runnable) with "SetTestClass2impl" on remote thread.
+"SetTestClass2impl" on public static void art.Test1913.InterfaceMethod(java.lang.Runnable) set value: TestClass2impl("TestClass2("Set TestClass2impl")")
+	Value is 'TestClass2impl("TestClass2("Set TestClass2impl")")' (class: class art.Test1913$TestClass2impl)
+Running public static void art.Test1913.SpecificClassMethod(java.lang.Runnable) with "GetGetObject" on remote thread.
+"GetGetObject" on public static void art.Test1913.SpecificClassMethod(java.lang.Runnable) got value: TestClass1("SpecificClassMethod")
+	Value is 'TestClass1("SpecificClassMethod")' (class: class art.Test1913$TestClass1)
+Running public static void art.Test1913.SpecificClassMethod(java.lang.Runnable) with "SetNull" on remote thread.
+"SetNull" on public static void art.Test1913.SpecificClassMethod(java.lang.Runnable) set value: null
+	Value is 'null' (class: NULL)
+Running public static void art.Test1913.SpecificClassMethod(java.lang.Runnable) with "SetTestClass1" on remote thread.
+"SetTestClass1" on public static void art.Test1913.SpecificClassMethod(java.lang.Runnable) set value: TestClass1("Set TestClass1")
+	Value is 'TestClass1("Set TestClass1")' (class: class art.Test1913$TestClass1)
+Running public static void art.Test1913.SpecificClassMethod(java.lang.Runnable) with "SetTestClass1ext" on remote thread.
+"SetTestClass1ext" on public static void art.Test1913.SpecificClassMethod(java.lang.Runnable) set value: TestClass1ext("TestClass1("Set TestClass1ext")")
+	Value is 'TestClass1ext("TestClass1("Set TestClass1ext")")' (class: class art.Test1913$TestClass1ext)
+Running public static void art.Test1913.SpecificClassMethod(java.lang.Runnable) with "SetTestClass2" on remote thread.
+"SetTestClass2" on public static void art.Test1913.SpecificClassMethod(java.lang.Runnable) failed to set value TestClass2("Set TestClass2") due to JVMTI_ERROR_TYPE_MISMATCH
+	Value is 'TestClass1("SpecificClassMethod")' (class: class art.Test1913$TestClass1)
+Running public static void art.Test1913.SpecificClassMethod(java.lang.Runnable) with "SetTestClass2impl" on remote thread.
+"SetTestClass2impl" on public static void art.Test1913.SpecificClassMethod(java.lang.Runnable) failed to set value TestClass2impl("TestClass2("Set TestClass2impl")") due to JVMTI_ERROR_TYPE_MISMATCH
+	Value is 'TestClass1("SpecificClassMethod")' (class: class art.Test1913$TestClass1)
+Running public static void art.Test1913.PrimitiveMethod(java.lang.Runnable) with "GetGetObject" on remote thread.
+"GetGetObject" on public static void art.Test1913.PrimitiveMethod(java.lang.Runnable) failed due to JVMTI_ERROR_TYPE_MISMATCH
+	Value is '42' (class: class java.lang.Integer)
+Running public static void art.Test1913.PrimitiveMethod(java.lang.Runnable) with "SetNull" on remote thread.
+"SetNull" on public static void art.Test1913.PrimitiveMethod(java.lang.Runnable) failed to set value null due to JVMTI_ERROR_TYPE_MISMATCH
+	Value is '42' (class: class java.lang.Integer)
+Running public static void art.Test1913.PrimitiveMethod(java.lang.Runnable) with "SetTestClass1" on remote thread.
+"SetTestClass1" on public static void art.Test1913.PrimitiveMethod(java.lang.Runnable) failed to set value TestClass1("Set TestClass1") due to JVMTI_ERROR_TYPE_MISMATCH
+	Value is '42' (class: class java.lang.Integer)
+Running public static void art.Test1913.PrimitiveMethod(java.lang.Runnable) with "SetTestClass1ext" on remote thread.
+"SetTestClass1ext" on public static void art.Test1913.PrimitiveMethod(java.lang.Runnable) failed to set value TestClass1ext("TestClass1("Set TestClass1ext")") due to JVMTI_ERROR_TYPE_MISMATCH
+	Value is '42' (class: class java.lang.Integer)
+Running public static void art.Test1913.PrimitiveMethod(java.lang.Runnable) with "SetTestClass2" on remote thread.
+"SetTestClass2" on public static void art.Test1913.PrimitiveMethod(java.lang.Runnable) failed to set value TestClass2("Set TestClass2") due to JVMTI_ERROR_TYPE_MISMATCH
+	Value is '42' (class: class java.lang.Integer)
+Running public static void art.Test1913.PrimitiveMethod(java.lang.Runnable) with "SetTestClass2impl" on remote thread.
+"SetTestClass2impl" on public static void art.Test1913.PrimitiveMethod(java.lang.Runnable) failed to set value TestClass2impl("TestClass2("Set TestClass2impl")") due to JVMTI_ERROR_TYPE_MISMATCH
+	Value is '42' (class: class java.lang.Integer)
diff --git a/test/1913-get-set-local-objects/info.txt b/test/1913-get-set-local-objects/info.txt
new file mode 100644
index 0000000..86ac743
--- /dev/null
+++ b/test/1913-get-set-local-objects/info.txt
@@ -0,0 +1,2 @@
+Tests for jvmti get and set local variable object.
+
diff --git a/test/1913-get-set-local-objects/run b/test/1913-get-set-local-objects/run
new file mode 100755
index 0000000..51875a7
--- /dev/null
+++ b/test/1913-get-set-local-objects/run
@@ -0,0 +1,18 @@
+#!/bin/bash
+#
+# Copyright 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Ask for stack traces to be dumped to a file rather than to stdout.
+./default-run "$@" --jvmti
diff --git a/test/1913-get-set-local-objects/src/Main.java b/test/1913-get-set-local-objects/src/Main.java
new file mode 100644
index 0000000..45565c2
--- /dev/null
+++ b/test/1913-get-set-local-objects/src/Main.java
@@ -0,0 +1,21 @@
+/*
+ * 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 {
+    art.Test1913.run();
+  }
+}
diff --git a/test/1913-get-set-local-objects/src/art/Breakpoint.java b/test/1913-get-set-local-objects/src/art/Breakpoint.java
new file mode 100644
index 0000000..bbb89f7
--- /dev/null
+++ b/test/1913-get-set-local-objects/src/art/Breakpoint.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package art;
+
+import java.lang.reflect.Executable;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.Objects;
+
+public class Breakpoint {
+  public static class Manager {
+    public static class BP {
+      public final Executable method;
+      public final long location;
+
+      public BP(Executable method) {
+        this(method, getStartLocation(method));
+      }
+
+      public BP(Executable method, long location) {
+        this.method = method;
+        this.location = location;
+      }
+
+      @Override
+      public boolean equals(Object other) {
+        return (other instanceof BP) &&
+            method.equals(((BP)other).method) &&
+            location == ((BP)other).location;
+      }
+
+      @Override
+      public String toString() {
+        return method.toString() + " @ " + getLine();
+      }
+
+      @Override
+      public int hashCode() {
+        return Objects.hash(method, location);
+      }
+
+      public int getLine() {
+        try {
+          LineNumber[] lines = getLineNumberTable(method);
+          int best = -1;
+          for (LineNumber l : lines) {
+            if (l.location > location) {
+              break;
+            } else {
+              best = l.line;
+            }
+          }
+          return best;
+        } catch (Exception e) {
+          return -1;
+        }
+      }
+    }
+
+    private Set<BP> breaks = new HashSet<>();
+
+    public void setBreakpoints(BP... bs) {
+      for (BP b : bs) {
+        if (breaks.add(b)) {
+          Breakpoint.setBreakpoint(b.method, b.location);
+        }
+      }
+    }
+    public void setBreakpoint(Executable method, long location) {
+      setBreakpoints(new BP(method, location));
+    }
+
+    public void clearBreakpoints(BP... bs) {
+      for (BP b : bs) {
+        if (breaks.remove(b)) {
+          Breakpoint.clearBreakpoint(b.method, b.location);
+        }
+      }
+    }
+    public void clearBreakpoint(Executable method, long location) {
+      clearBreakpoints(new BP(method, location));
+    }
+
+    public void clearAllBreakpoints() {
+      clearBreakpoints(breaks.toArray(new BP[0]));
+    }
+  }
+
+  public static void startBreakpointWatch(Class<?> methodClass,
+                                          Executable breakpointReached,
+                                          Thread thr) {
+    startBreakpointWatch(methodClass, breakpointReached, false, thr);
+  }
+
+  /**
+   * Enables the trapping of breakpoint events.
+   *
+   * If allowRecursive == true then breakpoints will be sent even if one is currently being handled.
+   */
+  public static native void startBreakpointWatch(Class<?> methodClass,
+                                                 Executable breakpointReached,
+                                                 boolean allowRecursive,
+                                                 Thread thr);
+  public static native void stopBreakpointWatch(Thread thr);
+
+  public static final class LineNumber implements Comparable<LineNumber> {
+    public final long location;
+    public final int line;
+
+    private LineNumber(long loc, int line) {
+      this.location = loc;
+      this.line = line;
+    }
+
+    public boolean equals(Object other) {
+      return other instanceof LineNumber && ((LineNumber)other).line == line &&
+          ((LineNumber)other).location == location;
+    }
+
+    public int compareTo(LineNumber other) {
+      int v = Integer.valueOf(line).compareTo(Integer.valueOf(other.line));
+      if (v != 0) {
+        return v;
+      } else {
+        return Long.valueOf(location).compareTo(Long.valueOf(other.location));
+      }
+    }
+  }
+
+  public static native void setBreakpoint(Executable m, long loc);
+  public static void setBreakpoint(Executable m, LineNumber l) {
+    setBreakpoint(m, l.location);
+  }
+
+  public static native void clearBreakpoint(Executable m, long loc);
+  public static void clearBreakpoint(Executable m, LineNumber l) {
+    clearBreakpoint(m, l.location);
+  }
+
+  private static native Object[] getLineNumberTableNative(Executable m);
+  public static LineNumber[] getLineNumberTable(Executable m) {
+    Object[] nativeTable = getLineNumberTableNative(m);
+    long[] location = (long[])(nativeTable[0]);
+    int[] lines = (int[])(nativeTable[1]);
+    if (lines.length != location.length) {
+      throw new Error("Lines and locations have different lengths!");
+    }
+    LineNumber[] out = new LineNumber[lines.length];
+    for (int i = 0; i < lines.length; i++) {
+      out[i] = new LineNumber(location[i], lines[i]);
+    }
+    return out;
+  }
+
+  public static native long getStartLocation(Executable m);
+
+  public static int locationToLine(Executable m, long location) {
+    try {
+      Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m);
+      int best = -1;
+      for (Breakpoint.LineNumber l : lines) {
+        if (l.location > location) {
+          break;
+        } else {
+          best = l.line;
+        }
+      }
+      return best;
+    } catch (Exception e) {
+      return -1;
+    }
+  }
+
+  public static long lineToLocation(Executable m, int line) throws Exception {
+    try {
+      Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m);
+      for (Breakpoint.LineNumber l : lines) {
+        if (l.line == line) {
+          return l.location;
+        }
+      }
+      throw new Exception("Unable to find line " + line + " in " + m);
+    } catch (Exception e) {
+      throw new Exception("Unable to get line number info for " + m, e);
+    }
+  }
+}
+
diff --git a/test/1913-get-set-local-objects/src/art/Locals.java b/test/1913-get-set-local-objects/src/art/Locals.java
new file mode 100644
index 0000000..22e21be
--- /dev/null
+++ b/test/1913-get-set-local-objects/src/art/Locals.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package art;
+
+import java.lang.reflect.Executable;
+import java.util.Objects;
+
+public class Locals {
+  public static native void EnableLocalVariableAccess();
+
+  public static class VariableDescription {
+    public final long start_location;
+    public final int length;
+    public final String name;
+    public final String signature;
+    public final String generic_signature;
+    public final int slot;
+
+    public VariableDescription(
+        long start, int length, String name, String sig, String gen_sig, int slot) {
+      this.start_location = start;
+      this.length = length;
+      this.name = name;
+      this.signature = sig;
+      this.generic_signature = gen_sig;
+      this.slot = slot;
+    }
+
+    @Override
+    public String toString() {
+      return String.format(
+          "VariableDescription { " +
+            "Sig: '%s', Name: '%s', Gen_sig: '%s', slot: %d, start: %d, len: %d" +
+          "}",
+          this.signature,
+          this.name,
+          this.generic_signature,
+          this.slot,
+          this.start_location,
+          this.length);
+    }
+    public boolean equals(Object other) {
+      if (!(other instanceof VariableDescription)) {
+        return false;
+      } else {
+        VariableDescription v = (VariableDescription)other;
+        return Objects.equals(v.signature, signature) &&
+            Objects.equals(v.name, name) &&
+            Objects.equals(v.generic_signature, generic_signature) &&
+            v.slot == slot &&
+            v.start_location == start_location &&
+            v.length == length;
+      }
+    }
+    public int hashCode() {
+      return Objects.hash(this.signature, this.name, this.generic_signature, this.slot,
+          this.start_location, this.length);
+    }
+  }
+
+  public static native VariableDescription[] GetLocalVariableTable(Executable e);
+
+  public static VariableDescription GetVariableAtLine(
+      Executable e, String name, String sig, int line) throws Exception {
+    return GetVariableAtLocation(e, name, sig, Breakpoint.lineToLocation(e, line));
+  }
+
+  public static VariableDescription GetVariableAtLocation(
+      Executable e, String name, String sig, long loc) {
+    VariableDescription[] vars = GetLocalVariableTable(e);
+    for (VariableDescription var : vars) {
+      if (var.start_location <= loc &&
+          var.length + var.start_location > loc &&
+          var.name.equals(name) &&
+          var.signature.equals(sig)) {
+        return var;
+      }
+    }
+    throw new Error(
+        "Unable to find variable " + name + " (sig: " + sig + ") in " + e + " at loc " + loc);
+  }
+
+  public static native int GetLocalVariableInt(Thread thr, int depth, int slot);
+  public static native long GetLocalVariableLong(Thread thr, int depth, int slot);
+  public static native float GetLocalVariableFloat(Thread thr, int depth, int slot);
+  public static native double GetLocalVariableDouble(Thread thr, int depth, int slot);
+  public static native Object GetLocalVariableObject(Thread thr, int depth, int slot);
+  public static native Object GetLocalInstance(Thread thr, int depth);
+
+  public static void SetLocalVariableInt(Thread thr, int depth, int slot, Object val) {
+    SetLocalVariableInt(thr, depth, slot, ((Number)val).intValue());
+  }
+  public static void SetLocalVariableLong(Thread thr, int depth, int slot, Object val) {
+    SetLocalVariableLong(thr, depth, slot, ((Number)val).longValue());
+  }
+  public static void SetLocalVariableFloat(Thread thr, int depth, int slot, Object val) {
+    SetLocalVariableFloat(thr, depth, slot, ((Number)val).floatValue());
+  }
+  public static void SetLocalVariableDouble(Thread thr, int depth, int slot, Object val) {
+    SetLocalVariableDouble(thr, depth, slot, ((Number)val).doubleValue());
+  }
+  public static native void SetLocalVariableInt(Thread thr, int depth, int slot, int val);
+  public static native void SetLocalVariableLong(Thread thr, int depth, int slot, long val);
+  public static native void SetLocalVariableFloat(Thread thr, int depth, int slot, float val);
+  public static native void SetLocalVariableDouble(Thread thr, int depth, int slot, double val);
+  public static native void SetLocalVariableObject(Thread thr, int depth, int slot, Object val);
+}
diff --git a/test/1913-get-set-local-objects/src/art/StackTrace.java b/test/1913-get-set-local-objects/src/art/StackTrace.java
new file mode 100644
index 0000000..2ea2f20
--- /dev/null
+++ b/test/1913-get-set-local-objects/src/art/StackTrace.java
@@ -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.
+ */
+
+package art;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Executable;
+
+public class StackTrace {
+  public static class StackFrameData {
+    public final Thread thr;
+    public final Executable method;
+    public final long current_location;
+    public final int depth;
+
+    public StackFrameData(Thread thr, Executable e, long loc, int depth) {
+      this.thr = thr;
+      this.method = e;
+      this.current_location = loc;
+      this.depth = depth;
+    }
+    @Override
+    public String toString() {
+      return String.format(
+          "StackFrameData { thr: '%s', method: '%s', loc: %d, depth: %d }",
+          this.thr,
+          this.method,
+          this.current_location,
+          this.depth);
+    }
+  }
+
+  public static native int GetStackDepth(Thread thr);
+
+  private static native StackFrameData[] nativeGetStackTrace(Thread thr);
+
+  public static StackFrameData[] GetStackTrace(Thread thr) {
+    // The RI seems to give inconsistent (and sometimes nonsensical) results if the thread is not
+    // suspended. The spec says that not being suspended is fine but since we want this to be
+    // consistent we will suspend for the RI.
+    boolean suspend_thread =
+        !System.getProperty("java.vm.name").equals("Dalvik") &&
+        !thr.equals(Thread.currentThread()) &&
+        !Suspension.isSuspended(thr);
+    if (suspend_thread) {
+      Suspension.suspend(thr);
+    }
+    StackFrameData[] out = nativeGetStackTrace(thr);
+    if (suspend_thread) {
+      Suspension.resume(thr);
+    }
+    return out;
+  }
+}
+
diff --git a/test/1913-get-set-local-objects/src/art/Suspension.java b/test/1913-get-set-local-objects/src/art/Suspension.java
new file mode 100644
index 0000000..16e62cc
--- /dev/null
+++ b/test/1913-get-set-local-objects/src/art/Suspension.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package art;
+
+public class Suspension {
+  // Suspends a thread using jvmti.
+  public native static void suspend(Thread thr);
+
+  // Resumes a thread using jvmti.
+  public native static void resume(Thread thr);
+
+  public native static boolean isSuspended(Thread thr);
+
+  public native static int[] suspendList(Thread... threads);
+  public native static int[] resumeList(Thread... threads);
+}
diff --git a/test/1913-get-set-local-objects/src/art/Test1913.java b/test/1913-get-set-local-objects/src/art/Test1913.java
new file mode 100644
index 0000000..417138a
--- /dev/null
+++ b/test/1913-get-set-local-objects/src/art/Test1913.java
@@ -0,0 +1,251 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package art;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Executable;
+import java.lang.reflect.Method;
+import java.nio.ByteBuffer;
+import java.util.concurrent.Semaphore;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+import java.util.function.Consumer;
+
+public class Test1913 {
+  public static final String TARGET_VAR = "TARGET";
+
+  public static interface TestInterface {
+    public default void doNothing() {}
+  }
+  public static class TestClass1 implements TestInterface {
+    public String id;
+    public TestClass1(String id) { this.id = id; }
+    public String toString() { return String.format("TestClass1(\"%s\")", id); }
+  }
+
+  public static class TestClass1ext extends TestClass1 {
+    public TestClass1ext(String id) { super(id); }
+    public String toString() { return String.format("TestClass1ext(\"%s\")", super.toString()); }
+  }
+  public static class TestClass2 {
+    public String id;
+    public TestClass2(String id) { this.id = id; }
+    public String toString() { return String.format("TestClass2(\"%s\")", id); }
+  }
+  public static class TestClass2impl extends TestClass2 implements TestInterface {
+    public TestClass2impl(String id) { super(id); }
+    public String toString() { return String.format("TestClass2impl(\"%s\")", super.toString()); }
+  }
+
+  public static void reportValue(Object val) {
+    System.out.println("\tValue is '" + val + "' (class: "
+        + (val != null ? val.getClass() : "NULL") + ")");
+  }
+
+  public static void PrimitiveMethod(Runnable safepoint) {
+    int TARGET = 42;
+    safepoint.run();
+    reportValue(TARGET);
+  }
+
+  // b/64115302: Needed to make sure that DX doesn't change the type of TARGET to TestClass1.
+  private static Object AsObject(Object o) { return o; }
+  public static void ObjectMethod(Runnable safepoint) {
+    Object TARGET = AsObject(new TestClass1("ObjectMethod"));
+    safepoint.run();
+    reportValue(TARGET);
+  }
+
+  public static void InterfaceMethod(Runnable safepoint) {
+    TestInterface TARGET = new TestClass1("InterfaceMethod");
+    safepoint.run();
+    reportValue(TARGET);
+  }
+
+  public static void SpecificClassMethod(Runnable safepoint) {
+    TestClass1 TARGET = new TestClass1("SpecificClassMethod");
+    safepoint.run();
+    reportValue(TARGET);
+  }
+
+  public static interface SafepointFunction {
+    public void invoke(
+        Thread thread,
+        Method target,
+        Locals.VariableDescription TARGET_desc,
+        int depth) throws Exception;
+  }
+
+  public static interface SetterFunction {
+    public void SetVar(Thread t, int depth, int slot, Object v);
+  }
+
+  public static interface GetterFunction {
+    public Object GetVar(Thread t, int depth, int slot);
+  }
+
+  public static SafepointFunction NamedSet(
+      final String type, final SetterFunction get, final Object v) {
+    return new SafepointFunction() {
+      public void invoke(Thread t, Method method, Locals.VariableDescription desc, int depth) {
+        try {
+          get.SetVar(t, depth, desc.slot, v);
+          System.out.println(this + " on " + method + " set value: " + v);
+        } catch (Exception e) {
+          System.out.println(
+              this + " on " + method + " failed to set value " + v + " due to " + e.getMessage());
+        }
+      }
+      public String toString() {
+        return "\"Set" + type + "\"";
+      }
+    };
+  }
+
+  public static SafepointFunction NamedGet(final String type, final GetterFunction get) {
+    return new SafepointFunction() {
+      public void invoke(Thread t, Method method, Locals.VariableDescription desc, int depth) {
+        try {
+          Object res = get.GetVar(t, depth, desc.slot);
+          System.out.println(this + " on " + method + " got value: " + res);
+        } catch (Exception e) {
+          System.out.println(this + " on " + method + " failed due to " + e.getMessage());
+        }
+      }
+      public String toString() {
+        return "\"Get" + type + "\"";
+      }
+    };
+  }
+
+  public static class TestCase {
+    public final Method target;
+
+    public TestCase(Method target) {
+      this.target = target;
+    }
+
+    public static class ThreadPauser implements Runnable {
+      public final Semaphore sem_wakeup_main;
+      public final Semaphore sem_wait;
+
+      public ThreadPauser() {
+        sem_wakeup_main = new Semaphore(0);
+        sem_wait = new Semaphore(0);
+      }
+
+      public void run() {
+        try {
+          sem_wakeup_main.release();
+          sem_wait.acquire();
+        } catch (Exception e) {
+          throw new Error("Error with semaphores!", e);
+        }
+      }
+
+      public void waitForOtherThreadToPause() throws Exception {
+        sem_wakeup_main.acquire();
+      }
+
+      public void wakeupOtherThread() throws Exception {
+        sem_wait.release();
+      }
+    }
+
+    public void exec(final SafepointFunction safepoint) throws Exception {
+      System.out.println("Running " + target + " with " + safepoint + " on remote thread.");
+      final ThreadPauser pause = new ThreadPauser();
+      Thread remote = new Thread(
+          () -> {
+            try {
+              target.invoke(null, pause);
+            } catch (Exception e) {
+              throw new Error("Error invoking remote thread " + Thread.currentThread(), e);
+            }
+          },
+          "remote thread for " + target + " with " + safepoint);
+      remote.start();
+      pause.waitForOtherThreadToPause();
+      try {
+        Suspension.suspend(remote);
+        StackTrace.StackFrameData frame = findStackFrame(remote);
+        Locals.VariableDescription desc = findTargetVar(frame.current_location);
+        safepoint.invoke(remote, target, desc, frame.depth);
+      } finally {
+        Suspension.resume(remote);
+        pause.wakeupOtherThread();
+        remote.join();
+      }
+    }
+
+    private Locals.VariableDescription findTargetVar(long loc) {
+      for (Locals.VariableDescription var : Locals.GetLocalVariableTable(target)) {
+        if (var.start_location <= loc &&
+            var.length + var.start_location > loc &&
+            var.name.equals(TARGET_VAR)) {
+          return var;
+        }
+      }
+      throw new Error(
+          "Unable to find variable " + TARGET_VAR + " in " + target + " at loc " + loc);
+    }
+
+    private StackTrace.StackFrameData findStackFrame(Thread thr) {
+      for (StackTrace.StackFrameData frame : StackTrace.GetStackTrace(thr)) {
+        if (frame.method.equals(target)) {
+          return frame;
+        }
+      }
+      throw new Error("Unable to find stack frame in method " + target + " on thread " + thr);
+    }
+  }
+  public static Method getMethod(String name) throws Exception {
+    return Test1913.class.getDeclaredMethod(name, Runnable.class);
+  }
+
+  public static void run() throws Exception {
+    Locals.EnableLocalVariableAccess();
+    final TestCase[] MAIN_TEST_CASES = new TestCase[] {
+      new TestCase(getMethod("ObjectMethod")),
+      new TestCase(getMethod("InterfaceMethod")),
+      new TestCase(getMethod("SpecificClassMethod")),
+      new TestCase(getMethod("PrimitiveMethod")),
+    };
+
+    final SetterFunction set_obj = Locals::SetLocalVariableObject;
+    final SafepointFunction[] SAFEPOINTS = new SafepointFunction[] {
+      NamedGet("GetObject",      Locals::GetLocalVariableObject),
+      NamedSet("Null",           set_obj, null),
+      NamedSet("TestClass1",     set_obj, new TestClass1("Set TestClass1")),
+      NamedSet("TestClass1ext",  set_obj, new TestClass1ext("Set TestClass1ext")),
+      NamedSet("TestClass2",     set_obj, new TestClass2("Set TestClass2")),
+      NamedSet("TestClass2impl", set_obj, new TestClass2impl("Set TestClass2impl")),
+    };
+
+    for (TestCase t: MAIN_TEST_CASES) {
+      for (SafepointFunction s : SAFEPOINTS) {
+        t.exec(s);
+      }
+    }
+  }
+}
+
diff --git a/test/1914-get-local-instance/expected.txt b/test/1914-get-local-instance/expected.txt
new file mode 100644
index 0000000..4117942
--- /dev/null
+++ b/test/1914-get-local-instance/expected.txt
@@ -0,0 +1,12 @@
+Running public static void art.Test1914.StaticMethod(java.lang.Runnable) with "GetThis" on remote thread.
+"GetThis" on public static void art.Test1914.StaticMethod(java.lang.Runnable) got value: null
+	Value is 'null' (class: NULL)
+Running public static native void art.Test1914.NativeStaticMethod(java.lang.Runnable) with "GetThis" on remote thread.
+"GetThis" on public static native void art.Test1914.NativeStaticMethod(java.lang.Runnable) got value: null
+	Value is 'null' (class: NULL)
+Running public void art.Test1914$TargetClass.InstanceMethod(java.lang.Runnable) with "GetThis" on remote thread.
+"GetThis" on public void art.Test1914$TargetClass.InstanceMethod(java.lang.Runnable) got value: TargetClass("InstanceMethodObject")
+	Value is 'TargetClass("InstanceMethodObject")' (class: class art.Test1914$TargetClass)
+Running public native void art.Test1914$TargetClass.NativeInstanceMethod(java.lang.Runnable) with "GetThis" on remote thread.
+"GetThis" on public native void art.Test1914$TargetClass.NativeInstanceMethod(java.lang.Runnable) got value: TargetClass("NativeInstanceMethodObject")
+	Value is 'TargetClass("NativeInstanceMethodObject")' (class: class art.Test1914$TargetClass)
diff --git a/test/1914-get-local-instance/info.txt b/test/1914-get-local-instance/info.txt
new file mode 100644
index 0000000..9fc3d62
--- /dev/null
+++ b/test/1914-get-local-instance/info.txt
@@ -0,0 +1,2 @@
+Test for jvmti get local instance
+
diff --git a/test/1914-get-local-instance/local_instance.cc b/test/1914-get-local-instance/local_instance.cc
new file mode 100644
index 0000000..03aa59e
--- /dev/null
+++ b/test/1914-get-local-instance/local_instance.cc
@@ -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.
+ */
+
+#include <iostream>
+#include <pthread.h>
+#include <stdio.h>
+#include <vector>
+
+#include "android-base/logging.h"
+#include "jni.h"
+#include "scoped_local_ref.h"
+#include "scoped_primitive_array.h"
+
+#include "jvmti.h"
+
+// Test infrastructure
+#include "jvmti_helper.h"
+#include "test_env.h"
+
+namespace art {
+namespace Test1914LocalInstance {
+
+extern "C" JNIEXPORT void Java_art_Test1914_00024TargetClass_NativeInstanceMethod(
+    JNIEnv* env, jobject thiz, jobject run) {
+  ScopedLocalRef<jclass> runnable(env, env->FindClass("java/lang/Runnable"));
+  if (env->ExceptionCheck()) { return; }
+  jmethodID method = env->GetMethodID(runnable.get(), "run", "()V");
+  if (env->ExceptionCheck()) { return; }
+  env->CallVoidMethod(run, method);
+  if (env->ExceptionCheck()) { return; }
+  ScopedLocalRef<jclass> Test1914(env, env->FindClass("art/Test1914"));
+  if (env->ExceptionCheck()) { return; }
+  jmethodID report = env->GetStaticMethodID(Test1914.get(), "reportValue", "(Ljava/lang/Object;)V");
+  if (env->ExceptionCheck()) { return; }
+  env->CallStaticVoidMethod(Test1914.get(), report, thiz);
+}
+
+extern "C" JNIEXPORT void Java_art_Test1914_NativeStaticMethod(
+    JNIEnv* env, jclass, jobject run) {
+  ScopedLocalRef<jclass> runnable(env, env->FindClass("java/lang/Runnable"));
+  if (env->ExceptionCheck()) { return; }
+  jmethodID method = env->GetMethodID(runnable.get(), "run", "()V");
+  if (env->ExceptionCheck()) { return; }
+  env->CallVoidMethod(run, method);
+  if (env->ExceptionCheck()) { return; }
+  ScopedLocalRef<jclass> Test1914(env, env->FindClass("art/Test1914"));
+  if (env->ExceptionCheck()) { return; }
+  jmethodID report = env->GetStaticMethodID(Test1914.get(), "reportValue", "(Ljava/lang/Object;)V");
+  if (env->ExceptionCheck()) { return; }
+  env->CallStaticVoidMethod(Test1914.get(), report, nullptr);
+}
+
+}  // namespace Test1914LocalInstance
+}  // namespace art
+
diff --git a/test/1914-get-local-instance/run b/test/1914-get-local-instance/run
new file mode 100755
index 0000000..51875a7
--- /dev/null
+++ b/test/1914-get-local-instance/run
@@ -0,0 +1,18 @@
+#!/bin/bash
+#
+# Copyright 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Ask for stack traces to be dumped to a file rather than to stdout.
+./default-run "$@" --jvmti
diff --git a/test/1914-get-local-instance/src/Main.java b/test/1914-get-local-instance/src/Main.java
new file mode 100644
index 0000000..163221e
--- /dev/null
+++ b/test/1914-get-local-instance/src/Main.java
@@ -0,0 +1,21 @@
+/*
+ * 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 {
+    art.Test1914.run();
+  }
+}
diff --git a/test/1914-get-local-instance/src/art/Breakpoint.java b/test/1914-get-local-instance/src/art/Breakpoint.java
new file mode 100644
index 0000000..bbb89f7
--- /dev/null
+++ b/test/1914-get-local-instance/src/art/Breakpoint.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package art;
+
+import java.lang.reflect.Executable;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.Objects;
+
+public class Breakpoint {
+  public static class Manager {
+    public static class BP {
+      public final Executable method;
+      public final long location;
+
+      public BP(Executable method) {
+        this(method, getStartLocation(method));
+      }
+
+      public BP(Executable method, long location) {
+        this.method = method;
+        this.location = location;
+      }
+
+      @Override
+      public boolean equals(Object other) {
+        return (other instanceof BP) &&
+            method.equals(((BP)other).method) &&
+            location == ((BP)other).location;
+      }
+
+      @Override
+      public String toString() {
+        return method.toString() + " @ " + getLine();
+      }
+
+      @Override
+      public int hashCode() {
+        return Objects.hash(method, location);
+      }
+
+      public int getLine() {
+        try {
+          LineNumber[] lines = getLineNumberTable(method);
+          int best = -1;
+          for (LineNumber l : lines) {
+            if (l.location > location) {
+              break;
+            } else {
+              best = l.line;
+            }
+          }
+          return best;
+        } catch (Exception e) {
+          return -1;
+        }
+      }
+    }
+
+    private Set<BP> breaks = new HashSet<>();
+
+    public void setBreakpoints(BP... bs) {
+      for (BP b : bs) {
+        if (breaks.add(b)) {
+          Breakpoint.setBreakpoint(b.method, b.location);
+        }
+      }
+    }
+    public void setBreakpoint(Executable method, long location) {
+      setBreakpoints(new BP(method, location));
+    }
+
+    public void clearBreakpoints(BP... bs) {
+      for (BP b : bs) {
+        if (breaks.remove(b)) {
+          Breakpoint.clearBreakpoint(b.method, b.location);
+        }
+      }
+    }
+    public void clearBreakpoint(Executable method, long location) {
+      clearBreakpoints(new BP(method, location));
+    }
+
+    public void clearAllBreakpoints() {
+      clearBreakpoints(breaks.toArray(new BP[0]));
+    }
+  }
+
+  public static void startBreakpointWatch(Class<?> methodClass,
+                                          Executable breakpointReached,
+                                          Thread thr) {
+    startBreakpointWatch(methodClass, breakpointReached, false, thr);
+  }
+
+  /**
+   * Enables the trapping of breakpoint events.
+   *
+   * If allowRecursive == true then breakpoints will be sent even if one is currently being handled.
+   */
+  public static native void startBreakpointWatch(Class<?> methodClass,
+                                                 Executable breakpointReached,
+                                                 boolean allowRecursive,
+                                                 Thread thr);
+  public static native void stopBreakpointWatch(Thread thr);
+
+  public static final class LineNumber implements Comparable<LineNumber> {
+    public final long location;
+    public final int line;
+
+    private LineNumber(long loc, int line) {
+      this.location = loc;
+      this.line = line;
+    }
+
+    public boolean equals(Object other) {
+      return other instanceof LineNumber && ((LineNumber)other).line == line &&
+          ((LineNumber)other).location == location;
+    }
+
+    public int compareTo(LineNumber other) {
+      int v = Integer.valueOf(line).compareTo(Integer.valueOf(other.line));
+      if (v != 0) {
+        return v;
+      } else {
+        return Long.valueOf(location).compareTo(Long.valueOf(other.location));
+      }
+    }
+  }
+
+  public static native void setBreakpoint(Executable m, long loc);
+  public static void setBreakpoint(Executable m, LineNumber l) {
+    setBreakpoint(m, l.location);
+  }
+
+  public static native void clearBreakpoint(Executable m, long loc);
+  public static void clearBreakpoint(Executable m, LineNumber l) {
+    clearBreakpoint(m, l.location);
+  }
+
+  private static native Object[] getLineNumberTableNative(Executable m);
+  public static LineNumber[] getLineNumberTable(Executable m) {
+    Object[] nativeTable = getLineNumberTableNative(m);
+    long[] location = (long[])(nativeTable[0]);
+    int[] lines = (int[])(nativeTable[1]);
+    if (lines.length != location.length) {
+      throw new Error("Lines and locations have different lengths!");
+    }
+    LineNumber[] out = new LineNumber[lines.length];
+    for (int i = 0; i < lines.length; i++) {
+      out[i] = new LineNumber(location[i], lines[i]);
+    }
+    return out;
+  }
+
+  public static native long getStartLocation(Executable m);
+
+  public static int locationToLine(Executable m, long location) {
+    try {
+      Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m);
+      int best = -1;
+      for (Breakpoint.LineNumber l : lines) {
+        if (l.location > location) {
+          break;
+        } else {
+          best = l.line;
+        }
+      }
+      return best;
+    } catch (Exception e) {
+      return -1;
+    }
+  }
+
+  public static long lineToLocation(Executable m, int line) throws Exception {
+    try {
+      Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m);
+      for (Breakpoint.LineNumber l : lines) {
+        if (l.line == line) {
+          return l.location;
+        }
+      }
+      throw new Exception("Unable to find line " + line + " in " + m);
+    } catch (Exception e) {
+      throw new Exception("Unable to get line number info for " + m, e);
+    }
+  }
+}
+
diff --git a/test/1914-get-local-instance/src/art/Locals.java b/test/1914-get-local-instance/src/art/Locals.java
new file mode 100644
index 0000000..22e21be
--- /dev/null
+++ b/test/1914-get-local-instance/src/art/Locals.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package art;
+
+import java.lang.reflect.Executable;
+import java.util.Objects;
+
+public class Locals {
+  public static native void EnableLocalVariableAccess();
+
+  public static class VariableDescription {
+    public final long start_location;
+    public final int length;
+    public final String name;
+    public final String signature;
+    public final String generic_signature;
+    public final int slot;
+
+    public VariableDescription(
+        long start, int length, String name, String sig, String gen_sig, int slot) {
+      this.start_location = start;
+      this.length = length;
+      this.name = name;
+      this.signature = sig;
+      this.generic_signature = gen_sig;
+      this.slot = slot;
+    }
+
+    @Override
+    public String toString() {
+      return String.format(
+          "VariableDescription { " +
+            "Sig: '%s', Name: '%s', Gen_sig: '%s', slot: %d, start: %d, len: %d" +
+          "}",
+          this.signature,
+          this.name,
+          this.generic_signature,
+          this.slot,
+          this.start_location,
+          this.length);
+    }
+    public boolean equals(Object other) {
+      if (!(other instanceof VariableDescription)) {
+        return false;
+      } else {
+        VariableDescription v = (VariableDescription)other;
+        return Objects.equals(v.signature, signature) &&
+            Objects.equals(v.name, name) &&
+            Objects.equals(v.generic_signature, generic_signature) &&
+            v.slot == slot &&
+            v.start_location == start_location &&
+            v.length == length;
+      }
+    }
+    public int hashCode() {
+      return Objects.hash(this.signature, this.name, this.generic_signature, this.slot,
+          this.start_location, this.length);
+    }
+  }
+
+  public static native VariableDescription[] GetLocalVariableTable(Executable e);
+
+  public static VariableDescription GetVariableAtLine(
+      Executable e, String name, String sig, int line) throws Exception {
+    return GetVariableAtLocation(e, name, sig, Breakpoint.lineToLocation(e, line));
+  }
+
+  public static VariableDescription GetVariableAtLocation(
+      Executable e, String name, String sig, long loc) {
+    VariableDescription[] vars = GetLocalVariableTable(e);
+    for (VariableDescription var : vars) {
+      if (var.start_location <= loc &&
+          var.length + var.start_location > loc &&
+          var.name.equals(name) &&
+          var.signature.equals(sig)) {
+        return var;
+      }
+    }
+    throw new Error(
+        "Unable to find variable " + name + " (sig: " + sig + ") in " + e + " at loc " + loc);
+  }
+
+  public static native int GetLocalVariableInt(Thread thr, int depth, int slot);
+  public static native long GetLocalVariableLong(Thread thr, int depth, int slot);
+  public static native float GetLocalVariableFloat(Thread thr, int depth, int slot);
+  public static native double GetLocalVariableDouble(Thread thr, int depth, int slot);
+  public static native Object GetLocalVariableObject(Thread thr, int depth, int slot);
+  public static native Object GetLocalInstance(Thread thr, int depth);
+
+  public static void SetLocalVariableInt(Thread thr, int depth, int slot, Object val) {
+    SetLocalVariableInt(thr, depth, slot, ((Number)val).intValue());
+  }
+  public static void SetLocalVariableLong(Thread thr, int depth, int slot, Object val) {
+    SetLocalVariableLong(thr, depth, slot, ((Number)val).longValue());
+  }
+  public static void SetLocalVariableFloat(Thread thr, int depth, int slot, Object val) {
+    SetLocalVariableFloat(thr, depth, slot, ((Number)val).floatValue());
+  }
+  public static void SetLocalVariableDouble(Thread thr, int depth, int slot, Object val) {
+    SetLocalVariableDouble(thr, depth, slot, ((Number)val).doubleValue());
+  }
+  public static native void SetLocalVariableInt(Thread thr, int depth, int slot, int val);
+  public static native void SetLocalVariableLong(Thread thr, int depth, int slot, long val);
+  public static native void SetLocalVariableFloat(Thread thr, int depth, int slot, float val);
+  public static native void SetLocalVariableDouble(Thread thr, int depth, int slot, double val);
+  public static native void SetLocalVariableObject(Thread thr, int depth, int slot, Object val);
+}
diff --git a/test/1914-get-local-instance/src/art/StackTrace.java b/test/1914-get-local-instance/src/art/StackTrace.java
new file mode 100644
index 0000000..2ea2f20
--- /dev/null
+++ b/test/1914-get-local-instance/src/art/StackTrace.java
@@ -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.
+ */
+
+package art;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Executable;
+
+public class StackTrace {
+  public static class StackFrameData {
+    public final Thread thr;
+    public final Executable method;
+    public final long current_location;
+    public final int depth;
+
+    public StackFrameData(Thread thr, Executable e, long loc, int depth) {
+      this.thr = thr;
+      this.method = e;
+      this.current_location = loc;
+      this.depth = depth;
+    }
+    @Override
+    public String toString() {
+      return String.format(
+          "StackFrameData { thr: '%s', method: '%s', loc: %d, depth: %d }",
+          this.thr,
+          this.method,
+          this.current_location,
+          this.depth);
+    }
+  }
+
+  public static native int GetStackDepth(Thread thr);
+
+  private static native StackFrameData[] nativeGetStackTrace(Thread thr);
+
+  public static StackFrameData[] GetStackTrace(Thread thr) {
+    // The RI seems to give inconsistent (and sometimes nonsensical) results if the thread is not
+    // suspended. The spec says that not being suspended is fine but since we want this to be
+    // consistent we will suspend for the RI.
+    boolean suspend_thread =
+        !System.getProperty("java.vm.name").equals("Dalvik") &&
+        !thr.equals(Thread.currentThread()) &&
+        !Suspension.isSuspended(thr);
+    if (suspend_thread) {
+      Suspension.suspend(thr);
+    }
+    StackFrameData[] out = nativeGetStackTrace(thr);
+    if (suspend_thread) {
+      Suspension.resume(thr);
+    }
+    return out;
+  }
+}
+
diff --git a/test/1914-get-local-instance/src/art/Suspension.java b/test/1914-get-local-instance/src/art/Suspension.java
new file mode 100644
index 0000000..16e62cc
--- /dev/null
+++ b/test/1914-get-local-instance/src/art/Suspension.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package art;
+
+public class Suspension {
+  // Suspends a thread using jvmti.
+  public native static void suspend(Thread thr);
+
+  // Resumes a thread using jvmti.
+  public native static void resume(Thread thr);
+
+  public native static boolean isSuspended(Thread thr);
+
+  public native static int[] suspendList(Thread... threads);
+  public native static int[] resumeList(Thread... threads);
+}
diff --git a/test/1914-get-local-instance/src/art/Test1914.java b/test/1914-get-local-instance/src/art/Test1914.java
new file mode 100644
index 0000000..c09f519
--- /dev/null
+++ b/test/1914-get-local-instance/src/art/Test1914.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package art;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Executable;
+import java.lang.reflect.Method;
+import java.nio.ByteBuffer;
+import java.util.concurrent.Semaphore;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+import java.util.function.Consumer;
+
+public class Test1914 {
+  public static final String TARGET_VAR = "TARGET";
+
+  public static void reportValue(Object val) {
+    System.out.println("\tValue is '" + val + "' (class: "
+        + (val != null ? val.getClass() : "NULL") + ")");
+  }
+
+  public static void StaticMethod(Runnable safepoint) {
+    safepoint.run();
+    reportValue(null);
+  }
+
+  public static native void NativeStaticMethod(Runnable safepoint);
+
+  public static class TargetClass {
+    public String id;
+    public String toString() { return String.format("TargetClass(\"%s\")", id); }
+    public TargetClass(String id) { this.id = id; }
+
+    public void InstanceMethod(Runnable safepoint) {
+      safepoint.run();
+      reportValue(this);
+    }
+
+    public native void NativeInstanceMethod(Runnable safepoint);
+  }
+
+  public static interface SafepointFunction {
+    public void invoke(
+        Thread thread,
+        Method target,
+        int depth) throws Exception;
+  }
+
+  public static interface GetterFunction {
+    public Object GetVar(Thread t, int depth);
+  }
+
+  public static SafepointFunction NamedGet(final String type, final GetterFunction get) {
+    return new SafepointFunction() {
+      public void invoke(Thread t, Method method, int depth) {
+        try {
+          Object res = get.GetVar(t, depth);
+          System.out.println(this + " on " + method + " got value: " + res);
+        } catch (Exception e) {
+          System.out.println(this + " on " + method + " failed due to " + e.getMessage());
+        }
+      }
+      public String toString() {
+        return "\"Get" + type + "\"";
+      }
+    };
+  }
+
+  public static class TestCase {
+    public final Object thiz;
+    public final Method target;
+
+    public TestCase(Method target) {
+      this(null, target);
+    }
+    public TestCase(Object thiz, Method target) {
+      this.thiz = thiz;
+      this.target = target;
+    }
+
+    public static class ThreadPauser implements Runnable {
+      public final Semaphore sem_wakeup_main;
+      public final Semaphore sem_wait;
+
+      public ThreadPauser() {
+        sem_wakeup_main = new Semaphore(0);
+        sem_wait = new Semaphore(0);
+      }
+
+      public void run() {
+        try {
+          sem_wakeup_main.release();
+          sem_wait.acquire();
+        } catch (Exception e) {
+          throw new Error("Error with semaphores!", e);
+        }
+      }
+
+      public void waitForOtherThreadToPause() throws Exception {
+        sem_wakeup_main.acquire();
+      }
+
+      public void wakeupOtherThread() throws Exception {
+        sem_wait.release();
+      }
+    }
+
+    public void exec(final SafepointFunction safepoint) throws Exception {
+      System.out.println("Running " + target + " with " + safepoint + " on remote thread.");
+      final ThreadPauser pause = new ThreadPauser();
+      Thread remote = new Thread(
+          () -> {
+            try {
+              target.invoke(thiz, pause);
+            } catch (Exception e) {
+              throw new Error("Error invoking remote thread " + Thread.currentThread(), e);
+            }
+          },
+          "remote thread for " + target + " with " + safepoint);
+      remote.start();
+      pause.waitForOtherThreadToPause();
+      try {
+        Suspension.suspend(remote);
+        StackTrace.StackFrameData frame = findStackFrame(remote);
+        safepoint.invoke(remote, target, frame.depth);
+      } finally {
+        Suspension.resume(remote);
+        pause.wakeupOtherThread();
+        remote.join();
+      }
+    }
+
+    private StackTrace.StackFrameData findStackFrame(Thread thr) {
+      for (StackTrace.StackFrameData frame : StackTrace.GetStackTrace(thr)) {
+        if (frame.method.equals(target)) {
+          return frame;
+        }
+      }
+      throw new Error("Unable to find stack frame in method " + target + " on thread " + thr);
+    }
+  }
+
+  public static Method getMethod(Class<?> klass, String name) throws Exception {
+    return klass.getDeclaredMethod(name, Runnable.class);
+  }
+
+  public static void run() throws Exception {
+    Locals.EnableLocalVariableAccess();
+    final TestCase[] MAIN_TEST_CASES = new TestCase[] {
+      new TestCase(null, getMethod(Test1914.class, "StaticMethod")),
+      new TestCase(null, getMethod(Test1914.class, "NativeStaticMethod")),
+      new TestCase(new TargetClass("InstanceMethodObject"),
+                   getMethod(TargetClass.class, "InstanceMethod")),
+      new TestCase(new TargetClass("NativeInstanceMethodObject"),
+                   getMethod(TargetClass.class, "NativeInstanceMethod")),
+    };
+
+    for (TestCase t: MAIN_TEST_CASES) {
+      t.exec(NamedGet("This", Locals::GetLocalInstance));
+    }
+  }
+}
+
diff --git a/test/1915-get-set-local-current-thread/expected.txt b/test/1915-get-set-local-current-thread/expected.txt
new file mode 100644
index 0000000..de39ca9
--- /dev/null
+++ b/test/1915-get-set-local-current-thread/expected.txt
@@ -0,0 +1,5 @@
+GetLocalInt on current thread!
+From GetLocalInt(), value is 42
+	Value is '42'
+SetLocalInt on current thread!
+	Value is '1337'
diff --git a/test/1915-get-set-local-current-thread/info.txt b/test/1915-get-set-local-current-thread/info.txt
new file mode 100644
index 0000000..c59f50d
--- /dev/null
+++ b/test/1915-get-set-local-current-thread/info.txt
@@ -0,0 +1,2 @@
+Tests for jvmti get/set Local variable on current thread.
+
diff --git a/test/1915-get-set-local-current-thread/run b/test/1915-get-set-local-current-thread/run
new file mode 100755
index 0000000..51875a7
--- /dev/null
+++ b/test/1915-get-set-local-current-thread/run
@@ -0,0 +1,18 @@
+#!/bin/bash
+#
+# Copyright 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Ask for stack traces to be dumped to a file rather than to stdout.
+./default-run "$@" --jvmti
diff --git a/test/1915-get-set-local-current-thread/src/Main.java b/test/1915-get-set-local-current-thread/src/Main.java
new file mode 100644
index 0000000..47e6767
--- /dev/null
+++ b/test/1915-get-set-local-current-thread/src/Main.java
@@ -0,0 +1,21 @@
+/*
+ * 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 {
+    art.Test1915.run();
+  }
+}
diff --git a/test/1915-get-set-local-current-thread/src/art/Breakpoint.java b/test/1915-get-set-local-current-thread/src/art/Breakpoint.java
new file mode 100644
index 0000000..bbb89f7
--- /dev/null
+++ b/test/1915-get-set-local-current-thread/src/art/Breakpoint.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package art;
+
+import java.lang.reflect.Executable;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.Objects;
+
+public class Breakpoint {
+  public static class Manager {
+    public static class BP {
+      public final Executable method;
+      public final long location;
+
+      public BP(Executable method) {
+        this(method, getStartLocation(method));
+      }
+
+      public BP(Executable method, long location) {
+        this.method = method;
+        this.location = location;
+      }
+
+      @Override
+      public boolean equals(Object other) {
+        return (other instanceof BP) &&
+            method.equals(((BP)other).method) &&
+            location == ((BP)other).location;
+      }
+
+      @Override
+      public String toString() {
+        return method.toString() + " @ " + getLine();
+      }
+
+      @Override
+      public int hashCode() {
+        return Objects.hash(method, location);
+      }
+
+      public int getLine() {
+        try {
+          LineNumber[] lines = getLineNumberTable(method);
+          int best = -1;
+          for (LineNumber l : lines) {
+            if (l.location > location) {
+              break;
+            } else {
+              best = l.line;
+            }
+          }
+          return best;
+        } catch (Exception e) {
+          return -1;
+        }
+      }
+    }
+
+    private Set<BP> breaks = new HashSet<>();
+
+    public void setBreakpoints(BP... bs) {
+      for (BP b : bs) {
+        if (breaks.add(b)) {
+          Breakpoint.setBreakpoint(b.method, b.location);
+        }
+      }
+    }
+    public void setBreakpoint(Executable method, long location) {
+      setBreakpoints(new BP(method, location));
+    }
+
+    public void clearBreakpoints(BP... bs) {
+      for (BP b : bs) {
+        if (breaks.remove(b)) {
+          Breakpoint.clearBreakpoint(b.method, b.location);
+        }
+      }
+    }
+    public void clearBreakpoint(Executable method, long location) {
+      clearBreakpoints(new BP(method, location));
+    }
+
+    public void clearAllBreakpoints() {
+      clearBreakpoints(breaks.toArray(new BP[0]));
+    }
+  }
+
+  public static void startBreakpointWatch(Class<?> methodClass,
+                                          Executable breakpointReached,
+                                          Thread thr) {
+    startBreakpointWatch(methodClass, breakpointReached, false, thr);
+  }
+
+  /**
+   * Enables the trapping of breakpoint events.
+   *
+   * If allowRecursive == true then breakpoints will be sent even if one is currently being handled.
+   */
+  public static native void startBreakpointWatch(Class<?> methodClass,
+                                                 Executable breakpointReached,
+                                                 boolean allowRecursive,
+                                                 Thread thr);
+  public static native void stopBreakpointWatch(Thread thr);
+
+  public static final class LineNumber implements Comparable<LineNumber> {
+    public final long location;
+    public final int line;
+
+    private LineNumber(long loc, int line) {
+      this.location = loc;
+      this.line = line;
+    }
+
+    public boolean equals(Object other) {
+      return other instanceof LineNumber && ((LineNumber)other).line == line &&
+          ((LineNumber)other).location == location;
+    }
+
+    public int compareTo(LineNumber other) {
+      int v = Integer.valueOf(line).compareTo(Integer.valueOf(other.line));
+      if (v != 0) {
+        return v;
+      } else {
+        return Long.valueOf(location).compareTo(Long.valueOf(other.location));
+      }
+    }
+  }
+
+  public static native void setBreakpoint(Executable m, long loc);
+  public static void setBreakpoint(Executable m, LineNumber l) {
+    setBreakpoint(m, l.location);
+  }
+
+  public static native void clearBreakpoint(Executable m, long loc);
+  public static void clearBreakpoint(Executable m, LineNumber l) {
+    clearBreakpoint(m, l.location);
+  }
+
+  private static native Object[] getLineNumberTableNative(Executable m);
+  public static LineNumber[] getLineNumberTable(Executable m) {
+    Object[] nativeTable = getLineNumberTableNative(m);
+    long[] location = (long[])(nativeTable[0]);
+    int[] lines = (int[])(nativeTable[1]);
+    if (lines.length != location.length) {
+      throw new Error("Lines and locations have different lengths!");
+    }
+    LineNumber[] out = new LineNumber[lines.length];
+    for (int i = 0; i < lines.length; i++) {
+      out[i] = new LineNumber(location[i], lines[i]);
+    }
+    return out;
+  }
+
+  public static native long getStartLocation(Executable m);
+
+  public static int locationToLine(Executable m, long location) {
+    try {
+      Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m);
+      int best = -1;
+      for (Breakpoint.LineNumber l : lines) {
+        if (l.location > location) {
+          break;
+        } else {
+          best = l.line;
+        }
+      }
+      return best;
+    } catch (Exception e) {
+      return -1;
+    }
+  }
+
+  public static long lineToLocation(Executable m, int line) throws Exception {
+    try {
+      Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m);
+      for (Breakpoint.LineNumber l : lines) {
+        if (l.line == line) {
+          return l.location;
+        }
+      }
+      throw new Exception("Unable to find line " + line + " in " + m);
+    } catch (Exception e) {
+      throw new Exception("Unable to get line number info for " + m, e);
+    }
+  }
+}
+
diff --git a/test/1915-get-set-local-current-thread/src/art/Locals.java b/test/1915-get-set-local-current-thread/src/art/Locals.java
new file mode 100644
index 0000000..22e21be
--- /dev/null
+++ b/test/1915-get-set-local-current-thread/src/art/Locals.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package art;
+
+import java.lang.reflect.Executable;
+import java.util.Objects;
+
+public class Locals {
+  public static native void EnableLocalVariableAccess();
+
+  public static class VariableDescription {
+    public final long start_location;
+    public final int length;
+    public final String name;
+    public final String signature;
+    public final String generic_signature;
+    public final int slot;
+
+    public VariableDescription(
+        long start, int length, String name, String sig, String gen_sig, int slot) {
+      this.start_location = start;
+      this.length = length;
+      this.name = name;
+      this.signature = sig;
+      this.generic_signature = gen_sig;
+      this.slot = slot;
+    }
+
+    @Override
+    public String toString() {
+      return String.format(
+          "VariableDescription { " +
+            "Sig: '%s', Name: '%s', Gen_sig: '%s', slot: %d, start: %d, len: %d" +
+          "}",
+          this.signature,
+          this.name,
+          this.generic_signature,
+          this.slot,
+          this.start_location,
+          this.length);
+    }
+    public boolean equals(Object other) {
+      if (!(other instanceof VariableDescription)) {
+        return false;
+      } else {
+        VariableDescription v = (VariableDescription)other;
+        return Objects.equals(v.signature, signature) &&
+            Objects.equals(v.name, name) &&
+            Objects.equals(v.generic_signature, generic_signature) &&
+            v.slot == slot &&
+            v.start_location == start_location &&
+            v.length == length;
+      }
+    }
+    public int hashCode() {
+      return Objects.hash(this.signature, this.name, this.generic_signature, this.slot,
+          this.start_location, this.length);
+    }
+  }
+
+  public static native VariableDescription[] GetLocalVariableTable(Executable e);
+
+  public static VariableDescription GetVariableAtLine(
+      Executable e, String name, String sig, int line) throws Exception {
+    return GetVariableAtLocation(e, name, sig, Breakpoint.lineToLocation(e, line));
+  }
+
+  public static VariableDescription GetVariableAtLocation(
+      Executable e, String name, String sig, long loc) {
+    VariableDescription[] vars = GetLocalVariableTable(e);
+    for (VariableDescription var : vars) {
+      if (var.start_location <= loc &&
+          var.length + var.start_location > loc &&
+          var.name.equals(name) &&
+          var.signature.equals(sig)) {
+        return var;
+      }
+    }
+    throw new Error(
+        "Unable to find variable " + name + " (sig: " + sig + ") in " + e + " at loc " + loc);
+  }
+
+  public static native int GetLocalVariableInt(Thread thr, int depth, int slot);
+  public static native long GetLocalVariableLong(Thread thr, int depth, int slot);
+  public static native float GetLocalVariableFloat(Thread thr, int depth, int slot);
+  public static native double GetLocalVariableDouble(Thread thr, int depth, int slot);
+  public static native Object GetLocalVariableObject(Thread thr, int depth, int slot);
+  public static native Object GetLocalInstance(Thread thr, int depth);
+
+  public static void SetLocalVariableInt(Thread thr, int depth, int slot, Object val) {
+    SetLocalVariableInt(thr, depth, slot, ((Number)val).intValue());
+  }
+  public static void SetLocalVariableLong(Thread thr, int depth, int slot, Object val) {
+    SetLocalVariableLong(thr, depth, slot, ((Number)val).longValue());
+  }
+  public static void SetLocalVariableFloat(Thread thr, int depth, int slot, Object val) {
+    SetLocalVariableFloat(thr, depth, slot, ((Number)val).floatValue());
+  }
+  public static void SetLocalVariableDouble(Thread thr, int depth, int slot, Object val) {
+    SetLocalVariableDouble(thr, depth, slot, ((Number)val).doubleValue());
+  }
+  public static native void SetLocalVariableInt(Thread thr, int depth, int slot, int val);
+  public static native void SetLocalVariableLong(Thread thr, int depth, int slot, long val);
+  public static native void SetLocalVariableFloat(Thread thr, int depth, int slot, float val);
+  public static native void SetLocalVariableDouble(Thread thr, int depth, int slot, double val);
+  public static native void SetLocalVariableObject(Thread thr, int depth, int slot, Object val);
+}
diff --git a/test/1915-get-set-local-current-thread/src/art/StackTrace.java b/test/1915-get-set-local-current-thread/src/art/StackTrace.java
new file mode 100644
index 0000000..2ea2f20
--- /dev/null
+++ b/test/1915-get-set-local-current-thread/src/art/StackTrace.java
@@ -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.
+ */
+
+package art;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Executable;
+
+public class StackTrace {
+  public static class StackFrameData {
+    public final Thread thr;
+    public final Executable method;
+    public final long current_location;
+    public final int depth;
+
+    public StackFrameData(Thread thr, Executable e, long loc, int depth) {
+      this.thr = thr;
+      this.method = e;
+      this.current_location = loc;
+      this.depth = depth;
+    }
+    @Override
+    public String toString() {
+      return String.format(
+          "StackFrameData { thr: '%s', method: '%s', loc: %d, depth: %d }",
+          this.thr,
+          this.method,
+          this.current_location,
+          this.depth);
+    }
+  }
+
+  public static native int GetStackDepth(Thread thr);
+
+  private static native StackFrameData[] nativeGetStackTrace(Thread thr);
+
+  public static StackFrameData[] GetStackTrace(Thread thr) {
+    // The RI seems to give inconsistent (and sometimes nonsensical) results if the thread is not
+    // suspended. The spec says that not being suspended is fine but since we want this to be
+    // consistent we will suspend for the RI.
+    boolean suspend_thread =
+        !System.getProperty("java.vm.name").equals("Dalvik") &&
+        !thr.equals(Thread.currentThread()) &&
+        !Suspension.isSuspended(thr);
+    if (suspend_thread) {
+      Suspension.suspend(thr);
+    }
+    StackFrameData[] out = nativeGetStackTrace(thr);
+    if (suspend_thread) {
+      Suspension.resume(thr);
+    }
+    return out;
+  }
+}
+
diff --git a/test/1915-get-set-local-current-thread/src/art/Suspension.java b/test/1915-get-set-local-current-thread/src/art/Suspension.java
new file mode 100644
index 0000000..16e62cc
--- /dev/null
+++ b/test/1915-get-set-local-current-thread/src/art/Suspension.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package art;
+
+public class Suspension {
+  // Suspends a thread using jvmti.
+  public native static void suspend(Thread thr);
+
+  // Resumes a thread using jvmti.
+  public native static void resume(Thread thr);
+
+  public native static boolean isSuspended(Thread thr);
+
+  public native static int[] suspendList(Thread... threads);
+  public native static int[] resumeList(Thread... threads);
+}
diff --git a/test/1915-get-set-local-current-thread/src/art/Test1915.java b/test/1915-get-set-local-current-thread/src/art/Test1915.java
new file mode 100644
index 0000000..a99a487
--- /dev/null
+++ b/test/1915-get-set-local-current-thread/src/art/Test1915.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package art;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Executable;
+import java.lang.reflect.Method;
+import java.nio.ByteBuffer;
+import java.util.concurrent.Semaphore;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+import java.util.function.Consumer;
+
+public class Test1915 {
+  public static final int SET_VALUE = 1337;
+  public static final String TARGET_VAR = "TARGET";
+
+  public static void reportValue(Object val) {
+    System.out.println("\tValue is '" + val + "'");
+  }
+  public static interface ThrowRunnable {
+    public void run() throws Exception;
+  }
+
+  public static void IntMethod(ThrowRunnable safepoint) throws Exception {
+    int TARGET = 42;
+    safepoint.run();
+    reportValue(TARGET);
+  }
+
+  public static void run() throws Exception {
+    Locals.EnableLocalVariableAccess();
+    final Method target = Test1915.class.getDeclaredMethod("IntMethod", ThrowRunnable.class);
+    // Get Variable.
+    System.out.println("GetLocalInt on current thread!");
+    IntMethod(() -> {
+      StackTrace.StackFrameData frame = FindStackFrame(target);
+      int depth = FindExpectedFrameDepth(frame);
+      int slot = FindSlot(frame);
+      int value = Locals.GetLocalVariableInt(Thread.currentThread(), depth, slot);
+      System.out.println("From GetLocalInt(), value is " + value);
+    });
+    // Set Variable.
+    System.out.println("SetLocalInt on current thread!");
+    IntMethod(() -> {
+      StackTrace.StackFrameData frame = FindStackFrame(target);
+      int depth = FindExpectedFrameDepth(frame);
+      int slot = FindSlot(frame);
+      Locals.SetLocalVariableInt(Thread.currentThread(), depth, slot, SET_VALUE);
+    });
+  }
+
+  public static int FindSlot(StackTrace.StackFrameData frame) throws Exception {
+    long loc = frame.current_location;
+    for (Locals.VariableDescription var : Locals.GetLocalVariableTable(frame.method)) {
+      if (var.start_location <= loc &&
+          var.length + var.start_location > loc &&
+          var.name.equals(TARGET_VAR)) {
+        return var.slot;
+      }
+    }
+    throw new Error(
+        "Unable to find variable " + TARGET_VAR + " in " + frame.method + " at loc " + loc);
+  }
+
+  public static int FindExpectedFrameDepth(StackTrace.StackFrameData frame) throws Exception {
+    // Adjust the 'frame' depth since it is modified by:
+    // +1 for Get/SetLocalVariableInt in future.
+    // -1 for FindStackFrame
+    // -1 for GetStackTrace
+    // -1 for GetStackTraceNative
+    // ------------------------------
+    // -2
+    return frame.depth - 2;
+  }
+
+  private static StackTrace.StackFrameData FindStackFrame(Method target) {
+    for (StackTrace.StackFrameData frame : StackTrace.GetStackTrace(Thread.currentThread())) {
+      if (frame.method.equals(target)) {
+        return frame;
+      }
+    }
+    throw new Error("Unable to find stack frame in method " + target);
+  }
+}
+
diff --git a/test/1916-get-set-current-frame/expected.txt b/test/1916-get-set-current-frame/expected.txt
new file mode 100644
index 0000000..343d493
--- /dev/null
+++ b/test/1916-get-set-current-frame/expected.txt
@@ -0,0 +1,4 @@
+From GetLocalInt(), value is 42
+	Value is '42'
+Setting TARGET to 1337
+	Value is '1337'
diff --git a/test/1916-get-set-current-frame/info.txt b/test/1916-get-set-current-frame/info.txt
new file mode 100644
index 0000000..7342af7
--- /dev/null
+++ b/test/1916-get-set-current-frame/info.txt
@@ -0,0 +1,2 @@
+Tests for jvmti get/set Local variable in the currently executing method frame.
+
diff --git a/test/1916-get-set-current-frame/run b/test/1916-get-set-current-frame/run
new file mode 100755
index 0000000..51875a7
--- /dev/null
+++ b/test/1916-get-set-current-frame/run
@@ -0,0 +1,18 @@
+#!/bin/bash
+#
+# Copyright 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Ask for stack traces to be dumped to a file rather than to stdout.
+./default-run "$@" --jvmti
diff --git a/test/1916-get-set-current-frame/src/Main.java b/test/1916-get-set-current-frame/src/Main.java
new file mode 100644
index 0000000..7d0cd21
--- /dev/null
+++ b/test/1916-get-set-current-frame/src/Main.java
@@ -0,0 +1,21 @@
+/*
+ * 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 {
+    art.Test1916.run();
+  }
+}
diff --git a/test/1916-get-set-current-frame/src/art/Breakpoint.java b/test/1916-get-set-current-frame/src/art/Breakpoint.java
new file mode 100644
index 0000000..bbb89f7
--- /dev/null
+++ b/test/1916-get-set-current-frame/src/art/Breakpoint.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package art;
+
+import java.lang.reflect.Executable;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.Objects;
+
+public class Breakpoint {
+  public static class Manager {
+    public static class BP {
+      public final Executable method;
+      public final long location;
+
+      public BP(Executable method) {
+        this(method, getStartLocation(method));
+      }
+
+      public BP(Executable method, long location) {
+        this.method = method;
+        this.location = location;
+      }
+
+      @Override
+      public boolean equals(Object other) {
+        return (other instanceof BP) &&
+            method.equals(((BP)other).method) &&
+            location == ((BP)other).location;
+      }
+
+      @Override
+      public String toString() {
+        return method.toString() + " @ " + getLine();
+      }
+
+      @Override
+      public int hashCode() {
+        return Objects.hash(method, location);
+      }
+
+      public int getLine() {
+        try {
+          LineNumber[] lines = getLineNumberTable(method);
+          int best = -1;
+          for (LineNumber l : lines) {
+            if (l.location > location) {
+              break;
+            } else {
+              best = l.line;
+            }
+          }
+          return best;
+        } catch (Exception e) {
+          return -1;
+        }
+      }
+    }
+
+    private Set<BP> breaks = new HashSet<>();
+
+    public void setBreakpoints(BP... bs) {
+      for (BP b : bs) {
+        if (breaks.add(b)) {
+          Breakpoint.setBreakpoint(b.method, b.location);
+        }
+      }
+    }
+    public void setBreakpoint(Executable method, long location) {
+      setBreakpoints(new BP(method, location));
+    }
+
+    public void clearBreakpoints(BP... bs) {
+      for (BP b : bs) {
+        if (breaks.remove(b)) {
+          Breakpoint.clearBreakpoint(b.method, b.location);
+        }
+      }
+    }
+    public void clearBreakpoint(Executable method, long location) {
+      clearBreakpoints(new BP(method, location));
+    }
+
+    public void clearAllBreakpoints() {
+      clearBreakpoints(breaks.toArray(new BP[0]));
+    }
+  }
+
+  public static void startBreakpointWatch(Class<?> methodClass,
+                                          Executable breakpointReached,
+                                          Thread thr) {
+    startBreakpointWatch(methodClass, breakpointReached, false, thr);
+  }
+
+  /**
+   * Enables the trapping of breakpoint events.
+   *
+   * If allowRecursive == true then breakpoints will be sent even if one is currently being handled.
+   */
+  public static native void startBreakpointWatch(Class<?> methodClass,
+                                                 Executable breakpointReached,
+                                                 boolean allowRecursive,
+                                                 Thread thr);
+  public static native void stopBreakpointWatch(Thread thr);
+
+  public static final class LineNumber implements Comparable<LineNumber> {
+    public final long location;
+    public final int line;
+
+    private LineNumber(long loc, int line) {
+      this.location = loc;
+      this.line = line;
+    }
+
+    public boolean equals(Object other) {
+      return other instanceof LineNumber && ((LineNumber)other).line == line &&
+          ((LineNumber)other).location == location;
+    }
+
+    public int compareTo(LineNumber other) {
+      int v = Integer.valueOf(line).compareTo(Integer.valueOf(other.line));
+      if (v != 0) {
+        return v;
+      } else {
+        return Long.valueOf(location).compareTo(Long.valueOf(other.location));
+      }
+    }
+  }
+
+  public static native void setBreakpoint(Executable m, long loc);
+  public static void setBreakpoint(Executable m, LineNumber l) {
+    setBreakpoint(m, l.location);
+  }
+
+  public static native void clearBreakpoint(Executable m, long loc);
+  public static void clearBreakpoint(Executable m, LineNumber l) {
+    clearBreakpoint(m, l.location);
+  }
+
+  private static native Object[] getLineNumberTableNative(Executable m);
+  public static LineNumber[] getLineNumberTable(Executable m) {
+    Object[] nativeTable = getLineNumberTableNative(m);
+    long[] location = (long[])(nativeTable[0]);
+    int[] lines = (int[])(nativeTable[1]);
+    if (lines.length != location.length) {
+      throw new Error("Lines and locations have different lengths!");
+    }
+    LineNumber[] out = new LineNumber[lines.length];
+    for (int i = 0; i < lines.length; i++) {
+      out[i] = new LineNumber(location[i], lines[i]);
+    }
+    return out;
+  }
+
+  public static native long getStartLocation(Executable m);
+
+  public static int locationToLine(Executable m, long location) {
+    try {
+      Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m);
+      int best = -1;
+      for (Breakpoint.LineNumber l : lines) {
+        if (l.location > location) {
+          break;
+        } else {
+          best = l.line;
+        }
+      }
+      return best;
+    } catch (Exception e) {
+      return -1;
+    }
+  }
+
+  public static long lineToLocation(Executable m, int line) throws Exception {
+    try {
+      Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m);
+      for (Breakpoint.LineNumber l : lines) {
+        if (l.line == line) {
+          return l.location;
+        }
+      }
+      throw new Exception("Unable to find line " + line + " in " + m);
+    } catch (Exception e) {
+      throw new Exception("Unable to get line number info for " + m, e);
+    }
+  }
+}
+
diff --git a/test/1916-get-set-current-frame/src/art/Locals.java b/test/1916-get-set-current-frame/src/art/Locals.java
new file mode 100644
index 0000000..22e21be
--- /dev/null
+++ b/test/1916-get-set-current-frame/src/art/Locals.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package art;
+
+import java.lang.reflect.Executable;
+import java.util.Objects;
+
+public class Locals {
+  public static native void EnableLocalVariableAccess();
+
+  public static class VariableDescription {
+    public final long start_location;
+    public final int length;
+    public final String name;
+    public final String signature;
+    public final String generic_signature;
+    public final int slot;
+
+    public VariableDescription(
+        long start, int length, String name, String sig, String gen_sig, int slot) {
+      this.start_location = start;
+      this.length = length;
+      this.name = name;
+      this.signature = sig;
+      this.generic_signature = gen_sig;
+      this.slot = slot;
+    }
+
+    @Override
+    public String toString() {
+      return String.format(
+          "VariableDescription { " +
+            "Sig: '%s', Name: '%s', Gen_sig: '%s', slot: %d, start: %d, len: %d" +
+          "}",
+          this.signature,
+          this.name,
+          this.generic_signature,
+          this.slot,
+          this.start_location,
+          this.length);
+    }
+    public boolean equals(Object other) {
+      if (!(other instanceof VariableDescription)) {
+        return false;
+      } else {
+        VariableDescription v = (VariableDescription)other;
+        return Objects.equals(v.signature, signature) &&
+            Objects.equals(v.name, name) &&
+            Objects.equals(v.generic_signature, generic_signature) &&
+            v.slot == slot &&
+            v.start_location == start_location &&
+            v.length == length;
+      }
+    }
+    public int hashCode() {
+      return Objects.hash(this.signature, this.name, this.generic_signature, this.slot,
+          this.start_location, this.length);
+    }
+  }
+
+  public static native VariableDescription[] GetLocalVariableTable(Executable e);
+
+  public static VariableDescription GetVariableAtLine(
+      Executable e, String name, String sig, int line) throws Exception {
+    return GetVariableAtLocation(e, name, sig, Breakpoint.lineToLocation(e, line));
+  }
+
+  public static VariableDescription GetVariableAtLocation(
+      Executable e, String name, String sig, long loc) {
+    VariableDescription[] vars = GetLocalVariableTable(e);
+    for (VariableDescription var : vars) {
+      if (var.start_location <= loc &&
+          var.length + var.start_location > loc &&
+          var.name.equals(name) &&
+          var.signature.equals(sig)) {
+        return var;
+      }
+    }
+    throw new Error(
+        "Unable to find variable " + name + " (sig: " + sig + ") in " + e + " at loc " + loc);
+  }
+
+  public static native int GetLocalVariableInt(Thread thr, int depth, int slot);
+  public static native long GetLocalVariableLong(Thread thr, int depth, int slot);
+  public static native float GetLocalVariableFloat(Thread thr, int depth, int slot);
+  public static native double GetLocalVariableDouble(Thread thr, int depth, int slot);
+  public static native Object GetLocalVariableObject(Thread thr, int depth, int slot);
+  public static native Object GetLocalInstance(Thread thr, int depth);
+
+  public static void SetLocalVariableInt(Thread thr, int depth, int slot, Object val) {
+    SetLocalVariableInt(thr, depth, slot, ((Number)val).intValue());
+  }
+  public static void SetLocalVariableLong(Thread thr, int depth, int slot, Object val) {
+    SetLocalVariableLong(thr, depth, slot, ((Number)val).longValue());
+  }
+  public static void SetLocalVariableFloat(Thread thr, int depth, int slot, Object val) {
+    SetLocalVariableFloat(thr, depth, slot, ((Number)val).floatValue());
+  }
+  public static void SetLocalVariableDouble(Thread thr, int depth, int slot, Object val) {
+    SetLocalVariableDouble(thr, depth, slot, ((Number)val).doubleValue());
+  }
+  public static native void SetLocalVariableInt(Thread thr, int depth, int slot, int val);
+  public static native void SetLocalVariableLong(Thread thr, int depth, int slot, long val);
+  public static native void SetLocalVariableFloat(Thread thr, int depth, int slot, float val);
+  public static native void SetLocalVariableDouble(Thread thr, int depth, int slot, double val);
+  public static native void SetLocalVariableObject(Thread thr, int depth, int slot, Object val);
+}
diff --git a/test/1916-get-set-current-frame/src/art/StackTrace.java b/test/1916-get-set-current-frame/src/art/StackTrace.java
new file mode 100644
index 0000000..2ea2f20
--- /dev/null
+++ b/test/1916-get-set-current-frame/src/art/StackTrace.java
@@ -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.
+ */
+
+package art;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Executable;
+
+public class StackTrace {
+  public static class StackFrameData {
+    public final Thread thr;
+    public final Executable method;
+    public final long current_location;
+    public final int depth;
+
+    public StackFrameData(Thread thr, Executable e, long loc, int depth) {
+      this.thr = thr;
+      this.method = e;
+      this.current_location = loc;
+      this.depth = depth;
+    }
+    @Override
+    public String toString() {
+      return String.format(
+          "StackFrameData { thr: '%s', method: '%s', loc: %d, depth: %d }",
+          this.thr,
+          this.method,
+          this.current_location,
+          this.depth);
+    }
+  }
+
+  public static native int GetStackDepth(Thread thr);
+
+  private static native StackFrameData[] nativeGetStackTrace(Thread thr);
+
+  public static StackFrameData[] GetStackTrace(Thread thr) {
+    // The RI seems to give inconsistent (and sometimes nonsensical) results if the thread is not
+    // suspended. The spec says that not being suspended is fine but since we want this to be
+    // consistent we will suspend for the RI.
+    boolean suspend_thread =
+        !System.getProperty("java.vm.name").equals("Dalvik") &&
+        !thr.equals(Thread.currentThread()) &&
+        !Suspension.isSuspended(thr);
+    if (suspend_thread) {
+      Suspension.suspend(thr);
+    }
+    StackFrameData[] out = nativeGetStackTrace(thr);
+    if (suspend_thread) {
+      Suspension.resume(thr);
+    }
+    return out;
+  }
+}
+
diff --git a/test/1916-get-set-current-frame/src/art/Suspension.java b/test/1916-get-set-current-frame/src/art/Suspension.java
new file mode 100644
index 0000000..16e62cc
--- /dev/null
+++ b/test/1916-get-set-current-frame/src/art/Suspension.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package art;
+
+public class Suspension {
+  // Suspends a thread using jvmti.
+  public native static void suspend(Thread thr);
+
+  // Resumes a thread using jvmti.
+  public native static void resume(Thread thr);
+
+  public native static boolean isSuspended(Thread thr);
+
+  public native static int[] suspendList(Thread... threads);
+  public native static int[] resumeList(Thread... threads);
+}
diff --git a/test/1916-get-set-current-frame/src/art/Test1916.java b/test/1916-get-set-current-frame/src/art/Test1916.java
new file mode 100644
index 0000000..3e5bce2
--- /dev/null
+++ b/test/1916-get-set-current-frame/src/art/Test1916.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package art;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Executable;
+import java.lang.reflect.Method;
+import java.nio.ByteBuffer;
+import java.util.concurrent.Semaphore;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+import java.util.function.Consumer;
+
+public class Test1916 {
+  public static final int SET_VALUE = 1337;
+  public static final String TARGET_VAR = "TARGET";
+
+  public static void reportValue(Object val) {
+    System.out.println("\tValue is '" + val + "'");
+  }
+
+  public static class IntRunner implements Runnable {
+    private volatile boolean continueBusyLoop;
+    private volatile boolean inBusyLoop;
+    public IntRunner() {
+      this.continueBusyLoop = true;
+      this.inBusyLoop = false;
+    }
+    public void run() {
+      int TARGET = 42;
+      // We will suspend the thread during this loop.
+      while (continueBusyLoop) {
+        inBusyLoop = true;
+      }
+      reportValue(TARGET);
+    }
+    public void waitForBusyLoopStart() { while (!inBusyLoop) {} }
+    public void finish() { continueBusyLoop = false; }
+  }
+
+  public static void run() throws Exception {
+    Locals.EnableLocalVariableAccess();
+    runGet();
+    runSet();
+  }
+
+  public static void runGet() throws Exception {
+    Method target = IntRunner.class.getDeclaredMethod("run");
+    // Get Int
+    IntRunner int_runner = new IntRunner();
+    Thread target_get = new Thread(int_runner, "GetLocalInt - Target");
+    target_get.start();
+    int_runner.waitForBusyLoopStart();
+    try {
+      Suspension.suspend(target_get);
+    } catch (Exception e) {
+      System.out.println("FAIL: got " + e);
+      e.printStackTrace();
+      int_runner.finish();
+      target_get.join();
+      return;
+    }
+    try {
+      StackTrace.StackFrameData frame = FindStackFrame(target_get, target);
+      int depth = frame.depth;
+      if (depth != 0) { throw new Error("Expected depth 0 but got " + depth); }
+      int slot = FindSlot(frame);
+      int value = Locals.GetLocalVariableInt(target_get, depth, slot);
+      System.out.println("From GetLocalInt(), value is " + value);
+    } finally {
+      Suspension.resume(target_get);
+      int_runner.finish();
+      target_get.join();
+    }
+  }
+
+  public static void runSet() throws Exception {
+    Method target = IntRunner.class.getDeclaredMethod("run");
+    // Set Int
+    IntRunner int_runner = new IntRunner();
+    Thread target_set = new Thread(int_runner, "SetLocalInt - Target");
+    target_set.start();
+    int_runner.waitForBusyLoopStart();
+    try {
+      Suspension.suspend(target_set);
+    } catch (Exception e) {
+      System.out.println("FAIL: got " + e);
+      e.printStackTrace();
+      int_runner.finish();
+      target_set.join();
+      return;
+    }
+    try {
+      StackTrace.StackFrameData frame = FindStackFrame(target_set, target);
+      int depth = frame.depth;
+      if (depth != 0) { throw new Error("Expected depth 0 but got " + depth); }
+      int slot = FindSlot(frame);
+      System.out.println("Setting TARGET to " + SET_VALUE);
+      Locals.SetLocalVariableInt(target_set, depth, slot, SET_VALUE);
+    } finally {
+      Suspension.resume(target_set);
+      int_runner.finish();
+      target_set.join();
+    }
+  }
+
+  public static int FindSlot(StackTrace.StackFrameData frame) throws Exception {
+    long loc = frame.current_location;
+    for (Locals.VariableDescription var : Locals.GetLocalVariableTable(frame.method)) {
+      if (var.start_location <= loc &&
+          var.length + var.start_location > loc &&
+          var.name.equals(TARGET_VAR)) {
+        return var.slot;
+      }
+    }
+    throw new Error(
+        "Unable to find variable " + TARGET_VAR + " in " + frame.method + " at loc " + loc);
+  }
+
+  private static StackTrace.StackFrameData FindStackFrame(Thread thr, Method target) {
+    for (StackTrace.StackFrameData frame : StackTrace.GetStackTrace(thr)) {
+      if (frame.method.equals(target)) {
+        return frame;
+      }
+    }
+    throw new Error("Unable to find stack frame in method " + target + " on thread " + thr);
+  }
+}
+
diff --git a/test/1917-get-stack-frame/expected.txt b/test/1917-get-stack-frame/expected.txt
new file mode 100644
index 0000000..4c9efcf
--- /dev/null
+++ b/test/1917-get-stack-frame/expected.txt
@@ -0,0 +1,33 @@
+Recurring 5 times
+'private static native art.StackTrace$StackFrameData[] art.StackTrace.nativeGetStackTrace(java.lang.Thread)' line: -1
+'public static art.StackTrace$StackFrameData[] art.StackTrace.GetStackTrace(java.lang.Thread)' line: 60
+'public void art.Test1917$StackTraceGenerator.run()' line: 82
+'public void art.Test1917$RecurCount.doRecur(int)' line: 104
+'public void art.Test1917$RecurCount.doRecur(int)' line: 102
+'public void art.Test1917$RecurCount.doRecur(int)' line: 102
+'public void art.Test1917$RecurCount.doRecur(int)' line: 102
+'public void art.Test1917$RecurCount.doRecur(int)' line: 102
+'public void art.Test1917$RecurCount.doRecur(int)' line: 102
+'public void art.Test1917$RecurCount.run()' line: 97
+'public static void art.Test1917.run() throws java.lang.Exception' line: 133
+Recurring 5 times on another thread
+'private static native art.StackTrace$StackFrameData[] art.StackTrace.nativeGetStackTrace(java.lang.Thread)' line: -1
+'public static art.StackTrace$StackFrameData[] art.StackTrace.GetStackTrace(java.lang.Thread)' line: 60
+'public void art.Test1917$StackTraceGenerator.run()' line: 82
+'public void art.Test1917$RecurCount.doRecur(int)' line: 104
+'public void art.Test1917$RecurCount.doRecur(int)' line: 102
+'public void art.Test1917$RecurCount.doRecur(int)' line: 102
+'public void art.Test1917$RecurCount.doRecur(int)' line: 102
+'public void art.Test1917$RecurCount.doRecur(int)' line: 102
+'public void art.Test1917$RecurCount.doRecur(int)' line: 102
+'public void art.Test1917$RecurCount.run()' line: 97
+Recurring 5 times on another thread. Stack trace from main thread!
+'public void java.util.concurrent.Semaphore.acquire() throws java.lang.InterruptedException' line: <NOT-DETERMINISTIC>
+'public void art.Test1917$ThreadPauser.run()' line: 46
+'public void art.Test1917$RecurCount.doRecur(int)' line: 104
+'public void art.Test1917$RecurCount.doRecur(int)' line: 102
+'public void art.Test1917$RecurCount.doRecur(int)' line: 102
+'public void art.Test1917$RecurCount.doRecur(int)' line: 102
+'public void art.Test1917$RecurCount.doRecur(int)' line: 102
+'public void art.Test1917$RecurCount.doRecur(int)' line: 102
+'public void art.Test1917$RecurCount.run()' line: 97
diff --git a/test/1917-get-stack-frame/info.txt b/test/1917-get-stack-frame/info.txt
new file mode 100644
index 0000000..e72034a
--- /dev/null
+++ b/test/1917-get-stack-frame/info.txt
@@ -0,0 +1 @@
+Tests stack frame functions of jvmti
diff --git a/test/1917-get-stack-frame/run b/test/1917-get-stack-frame/run
new file mode 100755
index 0000000..51875a7
--- /dev/null
+++ b/test/1917-get-stack-frame/run
@@ -0,0 +1,18 @@
+#!/bin/bash
+#
+# Copyright 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Ask for stack traces to be dumped to a file rather than to stdout.
+./default-run "$@" --jvmti
diff --git a/test/1917-get-stack-frame/src/Main.java b/test/1917-get-stack-frame/src/Main.java
new file mode 100644
index 0000000..c055a5c
--- /dev/null
+++ b/test/1917-get-stack-frame/src/Main.java
@@ -0,0 +1,21 @@
+/*
+ * 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 {
+    art.Test1917.run();
+  }
+}
diff --git a/test/1917-get-stack-frame/src/art/Breakpoint.java b/test/1917-get-stack-frame/src/art/Breakpoint.java
new file mode 100644
index 0000000..bbb89f7
--- /dev/null
+++ b/test/1917-get-stack-frame/src/art/Breakpoint.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package art;
+
+import java.lang.reflect.Executable;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.Objects;
+
+public class Breakpoint {
+  public static class Manager {
+    public static class BP {
+      public final Executable method;
+      public final long location;
+
+      public BP(Executable method) {
+        this(method, getStartLocation(method));
+      }
+
+      public BP(Executable method, long location) {
+        this.method = method;
+        this.location = location;
+      }
+
+      @Override
+      public boolean equals(Object other) {
+        return (other instanceof BP) &&
+            method.equals(((BP)other).method) &&
+            location == ((BP)other).location;
+      }
+
+      @Override
+      public String toString() {
+        return method.toString() + " @ " + getLine();
+      }
+
+      @Override
+      public int hashCode() {
+        return Objects.hash(method, location);
+      }
+
+      public int getLine() {
+        try {
+          LineNumber[] lines = getLineNumberTable(method);
+          int best = -1;
+          for (LineNumber l : lines) {
+            if (l.location > location) {
+              break;
+            } else {
+              best = l.line;
+            }
+          }
+          return best;
+        } catch (Exception e) {
+          return -1;
+        }
+      }
+    }
+
+    private Set<BP> breaks = new HashSet<>();
+
+    public void setBreakpoints(BP... bs) {
+      for (BP b : bs) {
+        if (breaks.add(b)) {
+          Breakpoint.setBreakpoint(b.method, b.location);
+        }
+      }
+    }
+    public void setBreakpoint(Executable method, long location) {
+      setBreakpoints(new BP(method, location));
+    }
+
+    public void clearBreakpoints(BP... bs) {
+      for (BP b : bs) {
+        if (breaks.remove(b)) {
+          Breakpoint.clearBreakpoint(b.method, b.location);
+        }
+      }
+    }
+    public void clearBreakpoint(Executable method, long location) {
+      clearBreakpoints(new BP(method, location));
+    }
+
+    public void clearAllBreakpoints() {
+      clearBreakpoints(breaks.toArray(new BP[0]));
+    }
+  }
+
+  public static void startBreakpointWatch(Class<?> methodClass,
+                                          Executable breakpointReached,
+                                          Thread thr) {
+    startBreakpointWatch(methodClass, breakpointReached, false, thr);
+  }
+
+  /**
+   * Enables the trapping of breakpoint events.
+   *
+   * If allowRecursive == true then breakpoints will be sent even if one is currently being handled.
+   */
+  public static native void startBreakpointWatch(Class<?> methodClass,
+                                                 Executable breakpointReached,
+                                                 boolean allowRecursive,
+                                                 Thread thr);
+  public static native void stopBreakpointWatch(Thread thr);
+
+  public static final class LineNumber implements Comparable<LineNumber> {
+    public final long location;
+    public final int line;
+
+    private LineNumber(long loc, int line) {
+      this.location = loc;
+      this.line = line;
+    }
+
+    public boolean equals(Object other) {
+      return other instanceof LineNumber && ((LineNumber)other).line == line &&
+          ((LineNumber)other).location == location;
+    }
+
+    public int compareTo(LineNumber other) {
+      int v = Integer.valueOf(line).compareTo(Integer.valueOf(other.line));
+      if (v != 0) {
+        return v;
+      } else {
+        return Long.valueOf(location).compareTo(Long.valueOf(other.location));
+      }
+    }
+  }
+
+  public static native void setBreakpoint(Executable m, long loc);
+  public static void setBreakpoint(Executable m, LineNumber l) {
+    setBreakpoint(m, l.location);
+  }
+
+  public static native void clearBreakpoint(Executable m, long loc);
+  public static void clearBreakpoint(Executable m, LineNumber l) {
+    clearBreakpoint(m, l.location);
+  }
+
+  private static native Object[] getLineNumberTableNative(Executable m);
+  public static LineNumber[] getLineNumberTable(Executable m) {
+    Object[] nativeTable = getLineNumberTableNative(m);
+    long[] location = (long[])(nativeTable[0]);
+    int[] lines = (int[])(nativeTable[1]);
+    if (lines.length != location.length) {
+      throw new Error("Lines and locations have different lengths!");
+    }
+    LineNumber[] out = new LineNumber[lines.length];
+    for (int i = 0; i < lines.length; i++) {
+      out[i] = new LineNumber(location[i], lines[i]);
+    }
+    return out;
+  }
+
+  public static native long getStartLocation(Executable m);
+
+  public static int locationToLine(Executable m, long location) {
+    try {
+      Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m);
+      int best = -1;
+      for (Breakpoint.LineNumber l : lines) {
+        if (l.location > location) {
+          break;
+        } else {
+          best = l.line;
+        }
+      }
+      return best;
+    } catch (Exception e) {
+      return -1;
+    }
+  }
+
+  public static long lineToLocation(Executable m, int line) throws Exception {
+    try {
+      Breakpoint.LineNumber[] lines = Breakpoint.getLineNumberTable(m);
+      for (Breakpoint.LineNumber l : lines) {
+        if (l.line == line) {
+          return l.location;
+        }
+      }
+      throw new Exception("Unable to find line " + line + " in " + m);
+    } catch (Exception e) {
+      throw new Exception("Unable to get line number info for " + m, e);
+    }
+  }
+}
+
diff --git a/test/1917-get-stack-frame/src/art/StackTrace.java b/test/1917-get-stack-frame/src/art/StackTrace.java
new file mode 100644
index 0000000..b12c3df
--- /dev/null
+++ b/test/1917-get-stack-frame/src/art/StackTrace.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package art;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Executable;
+
+public class StackTrace {
+  public static class StackFrameData {
+    public final Thread thr;
+    public final Executable method;
+    public final long current_location;
+    public final int depth;
+
+    public StackFrameData(Thread thr, Executable e, long loc, int depth) {
+      this.thr = thr;
+      this.method = e;
+      this.current_location = loc;
+      this.depth = depth;
+    }
+    @Override
+    public String toString() {
+      return String.format(
+          "StackFrameData { thr: '%s', method: '%s', loc: %d, depth: %d }",
+          this.thr,
+          this.method,
+          this.current_location,
+          this.depth);
+    }
+  }
+
+  public static native int GetStackDepth(Thread thr);
+
+  private static native StackFrameData[] nativeGetStackTrace(Thread thr);
+
+  public static StackFrameData[] GetStackTrace(Thread thr) {
+    // The RI seems to give inconsistent (and sometimes nonsensical) results if the thread is not
+    // suspended. The spec says that not being suspended is fine but since we want this to be
+    // consistent we will suspend for the RI.
+    boolean suspend_thread =
+        !System.getProperty("java.vm.name").equals("Dalvik") &&
+        !thr.equals(Thread.currentThread());
+    if (suspend_thread) {
+      Suspension.suspend(thr);
+    }
+    StackFrameData[] out = nativeGetStackTrace(thr);
+    if (suspend_thread) {
+      Suspension.resume(thr);
+    }
+    return out;
+  }
+}
+
diff --git a/test/1917-get-stack-frame/src/art/Suspension.java b/test/1917-get-stack-frame/src/art/Suspension.java
new file mode 100644
index 0000000..16e62cc
--- /dev/null
+++ b/test/1917-get-stack-frame/src/art/Suspension.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package art;
+
+public class Suspension {
+  // Suspends a thread using jvmti.
+  public native static void suspend(Thread thr);
+
+  // Resumes a thread using jvmti.
+  public native static void resume(Thread thr);
+
+  public native static boolean isSuspended(Thread thr);
+
+  public native static int[] suspendList(Thread... threads);
+  public native static int[] resumeList(Thread... threads);
+}
diff --git a/test/1917-get-stack-frame/src/art/Test1917.java b/test/1917-get-stack-frame/src/art/Test1917.java
new file mode 100644
index 0000000..def7530
--- /dev/null
+++ b/test/1917-get-stack-frame/src/art/Test1917.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package art;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.lang.reflect.Executable;
+import java.lang.reflect.Method;
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.concurrent.Semaphore;
+import java.util.Vector;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+import java.util.function.Consumer;
+
+public class Test1917 {
+  public final static boolean TEST_PRINT_ALL = false;
+
+  public static class ThreadPauser implements Runnable {
+    public Semaphore sem_wakeup_main = new Semaphore(0);
+    public Semaphore sem_wait = new Semaphore(0);
+
+    public void run() {
+      try {
+        sem_wakeup_main.release();
+        sem_wait.acquire();
+      } catch (Exception e) {
+        throw new Error("Error with semaphores!", e);
+      }
+    }
+
+    public void waitForOtherThreadToPause() throws Exception {
+      sem_wakeup_main.acquire();
+      while (!sem_wait.hasQueuedThreads()) {}
+    }
+
+    public void wakeupOtherThread() throws Exception {
+      sem_wait.release();
+    }
+  }
+
+  public static class StackTraceGenerator implements Runnable {
+    private final Thread thr;
+    private final Consumer<StackTrace.StackFrameData> con;
+    public StackTraceGenerator(Thread thr, Consumer<StackTrace.StackFrameData> con) {
+      this.thr = thr;
+      this.con = con;
+    }
+
+    public StackTraceGenerator(Consumer<StackTrace.StackFrameData> con) {
+      this(null, con);
+    }
+
+    public Thread getThread() {
+      if (thr == null) {
+        return Thread.currentThread();
+      } else {
+        return thr;
+      }
+    }
+    public void run() {
+      for (StackTrace.StackFrameData s : StackTrace.GetStackTrace(getThread())) {
+        con.accept(s);
+      }
+    }
+  }
+
+  public static class RecurCount implements Runnable {
+    private final int cnt;
+    private final Runnable then;
+    public RecurCount(int cnt, Runnable then) {
+      this.cnt = cnt;
+      this.then = then;
+    }
+
+    public void run() {
+      doRecur(0);
+    }
+
+    public void doRecur(int n) {
+      if (n < cnt) {
+        doRecur(n + 1);
+      } else {
+        then.run();
+      }
+    }
+  }
+
+  public static Consumer<StackTrace.StackFrameData> makePrintStackFramesConsumer()
+      throws Exception {
+    final Method end_method = Test1917.class.getDeclaredMethod("run");
+    return new Consumer<StackTrace.StackFrameData>() {
+      public void accept(StackTrace.StackFrameData data) {
+        if (TEST_PRINT_ALL) {
+          System.out.println(data);
+        } else {
+          Package p = data.method.getDeclaringClass().getPackage();
+          // Filter out anything to do with the testing harness.
+          if (p != null && p.equals(Test1917.class.getPackage())) {
+            System.out.printf("'%s' line: %d\n",
+                data.method,
+                Breakpoint.locationToLine(data.method, data.current_location));
+          } else if (data.method.getDeclaringClass().equals(Semaphore.class)) {
+            System.out.printf("'%s' line: <NOT-DETERMINISTIC>\n", data.method);
+          }
+        }
+      }
+    };
+  }
+
+  public static void run() throws Exception {
+    System.out.println("Recurring 5 times");
+    new RecurCount(5, new StackTraceGenerator(makePrintStackFramesConsumer())).run();
+
+    System.out.println("Recurring 5 times on another thread");
+    Thread thr = new Thread(
+        new RecurCount(5, new StackTraceGenerator(makePrintStackFramesConsumer())));
+    thr.start();
+    thr.join();
+
+    System.out.println("Recurring 5 times on another thread. Stack trace from main thread!");
+    ThreadPauser pause = new ThreadPauser();
+    Thread thr2 = new Thread(new RecurCount(5, pause));
+    thr2.start();
+    pause.waitForOtherThreadToPause();
+    new StackTraceGenerator(thr2, makePrintStackFramesConsumer()).run();
+    pause.wakeupOtherThread();
+    thr2.join();
+  }
+}
diff --git a/test/550-checker-multiply-accumulate/src/Main.java b/test/550-checker-multiply-accumulate/src/Main.java
index 810f0fa..6fd9cdd 100644
--- a/test/550-checker-multiply-accumulate/src/Main.java
+++ b/test/550-checker-multiply-accumulate/src/Main.java
@@ -434,7 +434,7 @@
   /// CHECK-DAG:     VecMultiplyAccumulate kind:Add loop:<<Loop>>      outer_loop:none
 
   /// CHECK-START-ARM64: void Main.SimdMulAdd(int[], int[]) instruction_simplifier_arm64 (after)
-  /// CHECK-NOT:     VecMull
+  /// CHECK-NOT:     VecMul
   /// CHECK-NOT:     VecAdd
   public static void SimdMulAdd(int[] array1, int[] array2) {
     for (int j = 0; j < 100; j++) {
@@ -452,7 +452,7 @@
   /// CHECK-DAG:     VecMultiplyAccumulate kind:Sub loop:<<Loop>>      outer_loop:none
 
   /// CHECK-START-ARM64: void Main.SimdMulSub(int[], int[]) instruction_simplifier_arm64 (after)
-  /// CHECK-NOT:     VecMull
+  /// CHECK-NOT:     VecMul
   /// CHECK-NOT:     VecSub
   public static void SimdMulSub(int[] array1, int[] array2) {
     for (int j = 0; j < 100; j++) {
diff --git a/test/623-checker-loop-regressions/src/Main.java b/test/623-checker-loop-regressions/src/Main.java
index aca997e..fc7bcb2 100644
--- a/test/623-checker-loop-regressions/src/Main.java
+++ b/test/623-checker-loop-regressions/src/Main.java
@@ -426,6 +426,21 @@
     }
   }
 
+  // Environment of an instruction, removed during SimplifyInduction, should be adjusted.
+  //
+  /// CHECK-START: void Main.inductionMax(int[]) loop_optimization (before)
+  /// CHECK-DAG: Phi loop:<<Loop:B\d+>> outer_loop:none
+  /// CHECK-DAG: Phi loop:<<Loop>>      outer_loop:none
+  //
+  /// CHECK-START: void Main.inductionMax(int[]) loop_optimization (after)
+  /// CHECK-NOT: Phi
+  private static void inductionMax(int[] a) {
+   int s = 0;
+    for (int i = 0; i < 10; i++) {
+      s = Math.max(s, 5);
+    }
+  }
+
   public static void main(String[] args) {
     expectEquals(10, earlyExitFirst(-1));
     for (int i = 0; i <= 10; i++) {
@@ -539,6 +554,8 @@
       expectEquals((byte)(i + 1), b1[i]);
     }
 
+    inductionMax(yy);
+
     System.out.println("passed");
   }
 
diff --git a/test/660-clinit/expected.txt b/test/660-clinit/expected.txt
index e103a2c..9eb4941 100644
--- a/test/660-clinit/expected.txt
+++ b/test/660-clinit/expected.txt
@@ -1,4 +1,8 @@
 JNI_OnLoad called
+A.a: 5
+A.a: 10
+B.b: 10
+C.c: 10
 X: 4950
 Y: 5730
 str: Hello World!
diff --git a/test/660-clinit/profile b/test/660-clinit/profile
new file mode 100644
index 0000000..0239f22
--- /dev/null
+++ b/test/660-clinit/profile
@@ -0,0 +1,10 @@
+LMain;
+LClInit;
+LDay;
+LA;
+LB;
+LC;
+LG;
+LGs;
+LObjectRef;
+
diff --git a/test/660-clinit/run b/test/660-clinit/run
new file mode 100644
index 0000000..d24ef42
--- /dev/null
+++ b/test/660-clinit/run
@@ -0,0 +1,17 @@
+#!/bin/bash
+#
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+exec ${RUN} $@ --profile
diff --git a/test/660-clinit/src/Main.java b/test/660-clinit/src/Main.java
index f547692..f9b068e 100644
--- a/test/660-clinit/src/Main.java
+++ b/test/660-clinit/src/Main.java
@@ -26,7 +26,26 @@
     }
 
     expectNotPreInit(Day.class);
-    expectNotPreInit(ClInit.class);
+    expectNotPreInit(ClInit.class); // should pass
+    expectNotPreInit(A.class); // should pass
+    expectNotPreInit(B.class); // should fail
+    expectNotPreInit(C.class); // should fail
+    expectNotPreInit(G.class); // should fail
+    expectNotPreInit(Gs.class); // should fail
+    expectNotPreInit(Gss.class); // should fail
+
+    expectNotPreInit(Add.class);
+    expectNotPreInit(Mul.class);
+    expectNotPreInit(ObjectRef.class);
+
+    A x = new A();
+    System.out.println("A.a: " + A.a);
+
+    B y = new B();
+    C z = new C();
+    System.out.println("A.a: " + A.a);
+    System.out.println("B.b: " + B.b);
+    System.out.println("C.c: " + C.c);
 
     ClInit c = new ClInit();
     int aa = c.a;
@@ -113,3 +132,59 @@
   }
 }
 
+class A {
+  public static int a = 2;
+  static {
+    a = 5;  // self-updating, pass
+  }
+}
+
+class B {
+  public static int b;
+  static {
+    A.a = 10;  // write other's static field, fail
+    b = A.a;   // read other's static field, fail
+  }
+}
+
+class C {
+  public static int c;
+  static {
+    c = A.a; // read other's static field, fail
+  }
+}
+
+class G {
+  static G g;
+  static int i;
+  static {
+    g = new Gss(); // fail because recursive dependency
+    i = A.a;  // read other's static field, fail
+  }
+}
+
+// Gs will be successfully initialized as G's status is initializing at that point, which will
+// later aborted but Gs' transaction is already committed.
+// Instantiation of Gs will fail because we try to invoke G's <init>
+// but G's status will be StatusVerified. INVOKE_DIRECT will not initialize class.
+class Gs extends G {}  // fail because super class can't be initialized
+class Gss extends Gs {}
+
+// pruned because holding reference to non-image class
+class ObjectRef {
+  static Class<?> klazz[] = new Class<?>[]{Add.class, Mul.class};
+}
+
+// non-image
+class Add {
+  static int exec(int a, int b) {
+    return a + b;
+  }
+}
+
+// non-image
+class Mul {
+  static int exec(int a, int b) {
+    return a * b;
+  }
+}
diff --git a/test/Android.bp b/test/Android.bp
index 44cb4f6..fab664a 100644
--- a/test/Android.bp
+++ b/test/Android.bp
@@ -252,8 +252,10 @@
         "ti-agent/test_env.cc",
         "ti-agent/breakpoint_helper.cc",
         "ti-agent/common_helper.cc",
+        "ti-agent/locals_helper.cc",
         "ti-agent/redefinition_helper.cc",
         "ti-agent/suspension_helper.cc",
+        "ti-agent/stack_trace_helper.cc",
         "ti-agent/trace_helper.cc",
         // This is the list of non-special OnLoad things and excludes BCI and anything that depends
         // on ART internals.
@@ -292,6 +294,7 @@
         "1905-suspend-native/native_suspend.cc",
         "1908-suspend-native-resume-self/native_suspend_resume.cc",
         "1909-per-agent-tls/agent_tls.cc",
+        "1914-get-local-instance/local_instance.cc",
     ],
     shared_libs: [
         "libbase",
diff --git a/test/etc/run-test-jar b/test/etc/run-test-jar
index ede485a..e989e39 100755
--- a/test/etc/run-test-jar
+++ b/test/etc/run-test-jar
@@ -453,6 +453,10 @@
 
 if [ "$USE_JVM" = "y" ]; then
   export LD_LIBRARY_PATH=${ANDROID_HOST_OUT}/lib64
+  # Some jvmti tests are flaky without -Xint on the RI.
+  if [ "$IS_JVMTI_TEST" = "y" ]; then
+    FLAGS="${FLAGS} -Xint"
+  fi
   # Xmx is necessary since we don't pass down the ART flags to JVM.
   # We pass the classes2 path whether it's used (src-multidex) or not.
   cmdline="${JAVA} ${DEBUGGER_OPTS} ${JVM_VERIFY_ARG} -Xmx256m -classpath classes:classes2 ${FLAGS} $MAIN $@ ${ARGS}"
diff --git a/test/knownfailures.json b/test/knownfailures.json
index 09e76fa..5aa7923 100644
--- a/test/knownfailures.json
+++ b/test/knownfailures.json
@@ -707,16 +707,16 @@
         "variant": "gcstress & jit & target"
     },
     {
-        "tests": ["004-JniTest"],
-        "description": [ "Tests failing with --build-with-javac-dx since the new annotation",
-                         "lookup changes" ],
-        "bug": "b/63089991",
-        "env_vars": {"ANDROID_COMPILE_WITH_JACK": "false"}
-    },
-    {
         "tests": "660-clinit",
         "variant": "no-image | no-dex2oat | no-prebuild",
         "description": ["Tests <clinit> for app images, which --no-image, --no-prebuild and",
                         "--no-dex2oat do not create"]
+    },
+    {
+        "tests": ["961-default-iface-resolution-gen",
+                  "964-default-iface-init-gen",
+                  "968-default-partial-compile-gen"],
+        "env_vars": {"SANITIZE_HOST": "address"},
+        "description": ["Test hits dex2oat watchdog timeout (60sec) on art-asan"]
     }
 ]
diff --git a/test/run-test b/test/run-test
index 486b465..e6196a0 100755
--- a/test/run-test
+++ b/test/run-test
@@ -45,7 +45,7 @@
 export RUN="${progdir}/etc/run-test-jar"
 export DEX_LOCATION=/data/run-test/${test_dir}
 export NEED_DEX="true"
-export USE_JACK="true"
+export USE_JACK="false"
 export USE_DESUGAR="true"
 export SMALI_ARGS=""
 
@@ -926,6 +926,11 @@
             tail -n 3000 "$tmp_dir/$strace_output"
             echo '####################'
         fi
+        if [ "x$target_mode" = "xno" -a "x$SANITIZE_HOST" = "xaddress" ]; then
+            # Run the stack script to symbolize any ASAN aborts on the host for SANITIZE_HOST. The
+            # tools used by the given ABI work for both x86 and x86-64.
+            echo "ABI: 'x86_64'" | cat - "$output" | $ANDROID_BUILD_TOP/development/scripts/stack | tail -n 3000
+        fi
         echo ' '
     fi
 
diff --git a/test/testrunner/env.py b/test/testrunner/env.py
index b996b04..d45d009 100644
--- a/test/testrunner/env.py
+++ b/test/testrunner/env.py
@@ -33,7 +33,8 @@
                         'TARGET_ARCH',
                         'HOST_PREFER_32_BIT',
                         'HOST_OUT_EXECUTABLES',
-                        'ANDROID_JAVA_TOOLCHAIN']
+                        'ANDROID_JAVA_TOOLCHAIN',
+                        'ANDROID_COMPILE_WITH_JACK']
 _DUMP_MANY_VARS = None  # To be set to a dictionary with above list being the keys,
                         # and the build variable being the value.
 def _dump_many_vars(var_name):
@@ -78,6 +79,15 @@
 def _get_build_var(var_name):
   return _dump_many_vars(var_name)
 
+def _get_build_var_boolean(var, default):
+  val = _get_build_var(var)
+  if val:
+    if val == "True" or val == "true":
+      return True
+    if val == "False" or val == "false":
+      return False
+  return default
+
 def get_env(key):
   return _env.get(key)
 
@@ -97,7 +107,7 @@
 ANDROID_BUILD_TOP = _get_android_build_top()
 
 # Compiling with jack? Possible values in (True, False, 'default')
-ANDROID_COMPILE_WITH_JACK = _getEnvBoolean('ANDROID_COMPILE_WITH_JACK', 'default')
+ANDROID_COMPILE_WITH_JACK = _get_build_var_boolean('ANDROID_COMPILE_WITH_JACK', 'default')
 
 # Directory used for temporary test files on the host.
 ART_HOST_TEST_DIR = tempfile.mkdtemp(prefix = 'test-art-')
diff --git a/test/ti-agent/jvmti_helper.cc b/test/ti-agent/jvmti_helper.cc
index 51d3406..7280102 100644
--- a/test/ti-agent/jvmti_helper.cc
+++ b/test/ti-agent/jvmti_helper.cc
@@ -15,6 +15,7 @@
  */
 
 #include "jvmti_helper.h"
+#include "test_env.h"
 
 #include <dlfcn.h>
 
@@ -57,7 +58,7 @@
     .can_get_line_numbers                            = 1,
     .can_get_source_debug_extension                  = 1,
     .can_access_local_variables                      = 0,
-    .can_maintain_original_method_order              = 0,
+    .can_maintain_original_method_order              = 1,
     .can_generate_single_step_events                 = 1,
     .can_generate_exception_events                   = 0,
     .can_generate_frame_pop_events                   = 0,
@@ -90,6 +91,11 @@
 }
 
 void SetStandardCapabilities(jvmtiEnv* env) {
+  if (IsJVM()) {
+    // RI is more strict about adding capabilities at runtime then ART so just give it everything.
+    SetAllCapabilities(env);
+    return;
+  }
   jvmtiCapabilities caps = GetStandardCapabilities();
   CheckJvmtiError(env, env->AddCapabilities(&caps));
 }
@@ -100,7 +106,7 @@
   CheckJvmtiError(env, env->AddCapabilities(&caps));
 }
 
-bool JvmtiErrorToException(JNIEnv* env, jvmtiEnv* jvmti_env, jvmtiError error) {
+bool JvmtiErrorToException(JNIEnv* env, jvmtiEnv* jvmtienv, jvmtiError error) {
   if (error == JVMTI_ERROR_NONE) {
     return false;
   }
@@ -112,11 +118,11 @@
   }
 
   char* err;
-  CheckJvmtiError(jvmti_env, jvmti_env->GetErrorName(error, &err));
+  CheckJvmtiError(jvmtienv, jvmtienv->GetErrorName(error, &err));
 
   env->ThrowNew(rt_exception.get(), err);
 
-  Deallocate(jvmti_env, err);
+  Deallocate(jvmtienv, err);
   return true;
 }
 
diff --git a/test/ti-agent/jvmti_helper.h b/test/ti-agent/jvmti_helper.h
index 78d238a..a47a402 100644
--- a/test/ti-agent/jvmti_helper.h
+++ b/test/ti-agent/jvmti_helper.h
@@ -43,7 +43,7 @@
 
 // Convert the given error to a RuntimeException with a message derived from the error. Returns
 // true on error, false if error is JVMTI_ERROR_NONE.
-bool JvmtiErrorToException(JNIEnv* env, jvmtiEnv* jvmti_env, jvmtiError error);
+bool JvmtiErrorToException(JNIEnv* env, jvmtiEnv* jvmtienv, jvmtiError error);
 
 class JvmtiDeleter {
  public:
diff --git a/test/ti-agent/locals_helper.cc b/test/ti-agent/locals_helper.cc
new file mode 100644
index 0000000..e284b52
--- /dev/null
+++ b/test/ti-agent/locals_helper.cc
@@ -0,0 +1,210 @@
+/*
+ * 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 "common_helper.h"
+
+#include "jni.h"
+#include "jvmti.h"
+
+#include "jvmti_helper.h"
+#include "scoped_local_ref.h"
+#include "test_env.h"
+
+namespace art {
+namespace common_locals {
+
+static void DeallocateContents(jvmtiLocalVariableEntry* vars, jint nvars) {
+  for (jint i = 0; i < nvars; i++) {
+    jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(vars[i].name));
+    jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(vars[i].signature));
+    jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(vars[i].generic_signature));
+  }
+}
+
+extern "C" JNIEXPORT void Java_art_Locals_EnableLocalVariableAccess(JNIEnv* env, jclass) {
+  jvmtiCapabilities caps;
+  if (JvmtiErrorToException(env, jvmti_env, jvmti_env->GetCapabilities(&caps))) {
+    return;
+  }
+  caps.can_access_local_variables = 1;
+  JvmtiErrorToException(env, jvmti_env, jvmti_env->AddCapabilities(&caps));
+}
+
+extern "C" JNIEXPORT void Java_art_Locals_SetLocalVariableObject(JNIEnv* env,
+                                                                 jclass,
+                                                                 jthread t,
+                                                                 jint depth,
+                                                                 jint slot,
+                                                                 jobject val) {
+  JvmtiErrorToException(env, jvmti_env, jvmti_env->SetLocalObject(t, depth, slot, val));
+}
+
+extern "C" JNIEXPORT void Java_art_Locals_SetLocalVariableDouble(JNIEnv* env,
+                                                                 jclass,
+                                                                 jthread t,
+                                                                 jint depth,
+                                                                 jint slot,
+                                                                 jdouble val) {
+  JvmtiErrorToException(env, jvmti_env, jvmti_env->SetLocalDouble(t, depth, slot, val));
+}
+
+extern "C" JNIEXPORT void Java_art_Locals_SetLocalVariableFloat(JNIEnv* env,
+                                                                jclass,
+                                                                jthread t,
+                                                                jint depth,
+                                                                jint slot,
+                                                                jfloat val) {
+  JvmtiErrorToException(env, jvmti_env, jvmti_env->SetLocalFloat(t, depth, slot, val));
+}
+
+extern "C" JNIEXPORT void Java_art_Locals_SetLocalVariableLong(JNIEnv* env,
+                                                               jclass,
+                                                               jthread t,
+                                                               jint depth,
+                                                               jint slot,
+                                                               jlong val) {
+  JvmtiErrorToException(env, jvmti_env, jvmti_env->SetLocalLong(t, depth, slot, val));
+}
+
+extern "C" JNIEXPORT void Java_art_Locals_SetLocalVariableInt(JNIEnv* env,
+                                                              jclass,
+                                                              jthread t,
+                                                              jint depth,
+                                                              jint slot,
+                                                              jint val) {
+  JvmtiErrorToException(env, jvmti_env, jvmti_env->SetLocalInt(t, depth, slot, val));
+}
+
+extern "C" JNIEXPORT jdouble Java_art_Locals_GetLocalVariableDouble(JNIEnv* env,
+                                                                    jclass,
+                                                                    jthread t,
+                                                                    jint depth,
+                                                                    jint slot) {
+  jdouble ret = 0;
+  JvmtiErrorToException(env, jvmti_env, jvmti_env->GetLocalDouble(t, depth, slot, &ret));
+  return ret;
+}
+
+extern "C" JNIEXPORT jfloat Java_art_Locals_GetLocalVariableFloat(JNIEnv* env,
+                                                                  jclass,
+                                                                  jthread t,
+                                                                  jint depth,
+                                                                  jint slot) {
+  jfloat ret = 0;
+  JvmtiErrorToException(env, jvmti_env, jvmti_env->GetLocalFloat(t, depth, slot, &ret));
+  return ret;
+}
+
+extern "C" JNIEXPORT jlong Java_art_Locals_GetLocalVariableLong(JNIEnv* env,
+                                                                jclass,
+                                                                jthread t,
+                                                                jint depth,
+                                                                jint slot) {
+  jlong ret = 0;
+  JvmtiErrorToException(env, jvmti_env, jvmti_env->GetLocalLong(t, depth, slot, &ret));
+  return ret;
+}
+
+extern "C" JNIEXPORT jint Java_art_Locals_GetLocalVariableInt(JNIEnv* env,
+                                                              jclass,
+                                                              jthread t,
+                                                              jint depth,
+                                                              jint slot) {
+  jint ret = 0;
+  JvmtiErrorToException(env, jvmti_env, jvmti_env->GetLocalInt(t, depth, slot, &ret));
+  return ret;
+}
+
+extern "C" JNIEXPORT jobject Java_art_Locals_GetLocalInstance(JNIEnv* env,
+                                                              jclass,
+                                                              jthread t,
+                                                              jint depth) {
+  jobject ret = nullptr;
+  JvmtiErrorToException(env, jvmti_env, jvmti_env->GetLocalInstance(t, depth, &ret));
+  return ret;
+}
+
+extern "C" JNIEXPORT jobject Java_art_Locals_GetLocalVariableObject(JNIEnv* env,
+                                                                    jclass,
+                                                                    jthread t,
+                                                                    jint depth,
+                                                                    jint slot) {
+  jobject ret = nullptr;
+  JvmtiErrorToException(env, jvmti_env, jvmti_env->GetLocalObject(t, depth, slot, &ret));
+  return ret;
+}
+
+extern "C" JNIEXPORT jobjectArray Java_art_Locals_GetLocalVariableTable(JNIEnv* env,
+                                                                        jclass,
+                                                                        jobject m) {
+  jmethodID method = env->FromReflectedMethod(m);
+  if (env->ExceptionCheck()) {
+    return nullptr;
+  }
+  ScopedLocalRef<jclass> klass(env, env->FindClass("art/Locals$VariableDescription"));
+  if (env->ExceptionCheck()) {
+    return nullptr;
+  }
+  jint nvars;
+  jvmtiLocalVariableEntry* vars = nullptr;
+  if (JvmtiErrorToException(env, jvmti_env,
+                            jvmti_env->GetLocalVariableTable(method, &nvars, &vars))) {
+    return nullptr;
+  }
+  jobjectArray vars_array = env->NewObjectArray(nvars, klass.get(), nullptr);
+  if (env->ExceptionCheck()) {
+    DeallocateContents(vars, nvars);
+    jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(vars));
+    return nullptr;
+  }
+
+  jmethodID constructor = env->GetMethodID(
+      klass.get(), "<init>", "(JILjava/lang/String;Ljava/lang/String;Ljava/lang/String;I)V");
+  if (env->ExceptionCheck()) {
+    return nullptr;
+  }
+  for (jint i = 0; i < nvars; i++) {
+    ScopedLocalRef<jstring> name_string(env, env->NewStringUTF(vars[i].name));
+    ScopedLocalRef<jstring> sig_string(env, env->NewStringUTF(vars[i].signature));
+    ScopedLocalRef<jstring> generic_sig_string(env, env->NewStringUTF(vars[i].generic_signature));
+    jobject var_obj = env->NewObject(klass.get(),
+                                     constructor,
+                                     vars[i].start_location,
+                                     vars[i].length,
+                                     name_string.get(),
+                                     sig_string.get(),
+                                     generic_sig_string.get(),
+                                     vars[i].slot);
+    if (env->ExceptionCheck()) {
+      DeallocateContents(vars, nvars);
+      jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(vars));
+      return nullptr;
+    }
+    env->SetObjectArrayElement(vars_array, i, var_obj);
+    if (env->ExceptionCheck()) {
+      DeallocateContents(vars, nvars);
+      jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(vars));
+      return nullptr;
+    }
+  }
+
+  DeallocateContents(vars, nvars);
+  jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(vars));
+  return vars_array;
+}
+
+}  // namespace common_locals
+}  // namespace art
diff --git a/test/ti-agent/stack_trace_helper.cc b/test/ti-agent/stack_trace_helper.cc
new file mode 100644
index 0000000..f2a8e9a
--- /dev/null
+++ b/test/ti-agent/stack_trace_helper.cc
@@ -0,0 +1,99 @@
+
+/*
+ * 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 "common_helper.h"
+
+#include "jni.h"
+#include "jvmti.h"
+
+#include "jvmti_helper.h"
+#include "scoped_local_ref.h"
+#include "test_env.h"
+
+namespace art {
+namespace common_stack_trace {
+
+extern "C" JNIEXPORT jint JNICALL Java_art_StackTrace_GetStackDepth(
+    JNIEnv* env, jclass, jthread thr) {
+  jint ret;
+  JvmtiErrorToException(env, jvmti_env, jvmti_env->GetFrameCount(thr, &ret));
+  return ret;
+}
+
+extern "C" JNIEXPORT jobjectArray Java_art_StackTrace_nativeGetStackTrace(JNIEnv* env,
+                                                                          jclass,
+                                                                          jthread thr) {
+  jint depth;
+  ScopedLocalRef<jclass> klass(env, env->FindClass("art/StackTrace$StackFrameData"));
+  if (env->ExceptionCheck()) {
+    return nullptr;
+  }
+  jmethodID constructor = env->GetMethodID(
+      klass.get(), "<init>", "(Ljava/lang/Thread;Ljava/lang/reflect/Executable;JI)V");
+  if (env->ExceptionCheck()) {
+    return nullptr;
+  }
+  if (JvmtiErrorToException(env, jvmti_env, jvmti_env->GetFrameCount(thr, &depth))) {
+    return nullptr;
+  }
+  // Just give some extra space.
+  depth += 10;
+  jvmtiFrameInfo* frames;
+  if (JvmtiErrorToException(
+      env, jvmti_env, jvmti_env->Allocate(depth * sizeof(jvmtiFrameInfo),
+                                          reinterpret_cast<unsigned char**>(&frames)))) {
+    return nullptr;
+  }
+  jint nframes = 0;
+  if (JvmtiErrorToException(
+      env, jvmti_env, jvmti_env->GetStackTrace(thr, 0, depth, frames, &nframes))) {
+    jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(frames));
+    return nullptr;
+  }
+  jobjectArray frames_array = env->NewObjectArray(nframes, klass.get(), nullptr);
+  if (env->ExceptionCheck()) {
+    jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(frames));
+    return nullptr;
+  }
+  for (jint i = 0; i < nframes; i++) {
+    jobject jmethod = GetJavaMethod(jvmti_env, env, frames[i].method);
+    if (env->ExceptionCheck()) {
+      jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(frames));
+      return nullptr;
+    }
+    jobject frame_obj = env->NewObject(klass.get(),
+                                       constructor,
+                                       thr,
+                                       jmethod,
+                                       frames[i].location,
+                                       i);
+    if (env->ExceptionCheck()) {
+      jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(frames));
+      return nullptr;
+    }
+    env->SetObjectArrayElement(frames_array, i, frame_obj);
+    if (env->ExceptionCheck()) {
+      jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(frames));
+      return nullptr;
+    }
+  }
+  jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(frames));
+  return frames_array;
+}
+
+}  // namespace common_stack_trace
+}  // namespace art
diff --git a/test/ti-agent/trace_helper.cc b/test/ti-agent/trace_helper.cc
index 7a9d1e0..1f8ceff 100644
--- a/test/ti-agent/trace_helper.cc
+++ b/test/ti-agent/trace_helper.cc
@@ -388,10 +388,12 @@
   data->single_step = single_step != nullptr ? env->FromReflectedMethod(single_step) : nullptr;
   data->in_callback = false;
 
-  void* old_data = nullptr;
-  if (JvmtiErrorToException(env, jvmti_env, jvmti_env->GetEnvironmentLocalStorage(&old_data))) {
+  TraceData* old_data = nullptr;
+  if (JvmtiErrorToException(env, jvmti_env,
+                            jvmti_env->GetEnvironmentLocalStorage(
+                                reinterpret_cast<void**>(&old_data)))) {
     return;
-  } else if (old_data != nullptr) {
+  } else if (old_data != nullptr && old_data->test_klass != nullptr) {
     ScopedLocalRef<jclass> rt_exception(env, env->FindClass("java/lang/RuntimeException"));
     env->ThrowNew(rt_exception.get(), "Environment already has local storage set!");
     return;
@@ -455,6 +457,21 @@
 
 extern "C" JNIEXPORT void JNICALL Java_art_Trace_disableTracing(
     JNIEnv* env, jclass klass ATTRIBUTE_UNUSED, jthread thr) {
+  TraceData* data = nullptr;
+  if (JvmtiErrorToException(
+      env, jvmti_env, jvmti_env->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&data)))) {
+    return;
+  }
+  // If data is null then we haven't ever enabled tracing so we don't need to do anything.
+  if (data == nullptr || data->test_klass == nullptr) {
+    return;
+  }
+  env->DeleteGlobalRef(data->test_klass);
+  if (env->ExceptionCheck()) {
+    return;
+  }
+  // Clear test_klass so we know this isn't being used
+  data->test_klass = nullptr;
   if (JvmtiErrorToException(env, jvmti_env,
                             jvmti_env->SetEventNotificationMode(JVMTI_DISABLE,
                                                                 JVMTI_EVENT_FIELD_ACCESS,
diff --git a/tools/art b/tools/art
index bc0c85e..dea7ebd 100644
--- a/tools/art
+++ b/tools/art
@@ -17,7 +17,6 @@
 # Android (e.g. mksh).
 
 # Globals
-ARCHS={arm,arm64,mips,mips64,x86,x86_64}
 ART_BINARY=dalvikvm
 DELETE_ANDROID_DATA="no"
 LAUNCH_WRAPPER=
@@ -120,26 +119,96 @@
   env "$@"
 }
 
+# Parse a colon-separated list into an array (e.g. "foo.dex:bar.dex" -> (foo.dex bar.dex))
+PARSE_CLASSPATH_RESULT=()  # Return value will be here due to shell limitations.
+parse_classpath() {
+  local cp="$1"
+  local oldifs=$IFS
+
+  local cp_array
+  cp_array=()
+
+  IFS=":"
+  for part in $cp; do
+    cp_array+=("$part")
+  done
+  IFS=$oldifs
+
+  PARSE_CLASSPATH_RESULT=("${cp_array[@]}")
+}
+
+# Sets 'PARSE_CLASSPATH_RESULT' to an array of class path dex files.
+# e.g. (-cp foo/classes.dex:bar/classes.dex) -> (foo/classes.dex bar/classes.dex)
+find_cp_in_args() {
+  local found="false"
+  local index=0
+  local what
+
+  while [[ $# -gt 0 ]]; do
+    case "$1" in
+      -cp|-classpath)
+        parse_classpath "$2"
+        # Sets 'PARSE_CLASSPATH_RESULT' to an array of class path dex files.
+        # Subsequent parses will overwrite the preceding.
+        shift
+        ;;
+    esac
+    shift
+  done
+}
+
+# Delete the 'oat' directories relative to the classpath's dex files.
+# e.g. (foo/classes.dex bar/classes.dex) would delete (foo/oat bar/oat) directories.
+cleanup_oat_directory() {
+  local classpath
+  classpath=("$@")
+
+  local dirpath
+
+  for path in "${classpath[@]}"; do
+    dirpath="$(dirname "$path")"
+    [[ -d "$dirpath" ]] && verbose_run rm -rf "$dirpath/oat"
+  done
+}
+
+# Parse -cp <CP>, -classpath <CP>, and $CLASSPATH to find the dex files.
+# Each dex file's directory will have an 'oat' file directory, delete it.
+# Input: Command line arguments to the art script.
+# e.g. -cp foo/classes.dex:bar/classes.dex would delete (foo/oat bar/oat) directories.
+cleanup_oat_directory_for_classpath() {
+  # First try: Use $CLASSPATH environment variable.
+  parse_classpath "$CLASSPATH"
+  # Second try: Look for latest -cp or -classpath arg which will take precedence.
+  find_cp_in_args "$@"
+
+  cleanup_oat_directory "${PARSE_CLASSPATH_RESULT[@]}"
+}
+
+# Attempt to find $ANDROID_ROOT/framework/<isa>/core.art' without knowing what <isa> is.
+function check_if_boot_image_file_exists() {
+  local image_location_dir="$1"
+  local image_location_name="$2"
+
+  # Expand image_files to a list of existing image files on the disk.
+  # If no such files exist, it expands to single element 'dir/*/file' with a literal '*'.
+  local image_files
+  image_files=("$image_location_dir"/*/"$image_location_name") # avoid treating "*" as literal.
+
+  # Array always has at least 1 element. Test explicitly whether the file exists.
+  [[ -e "${image_files[0]}" ]]
+}
+
 # Automatically find the boot image location. It uses core.art by default.
 # On a real device, it might only have a boot.art, so use that instead when core.art does not exist.
 function detect_boot_image_location() {
   local image_location_dir="$ANDROID_ROOT/framework"
   local image_location_name="core.art"
 
-  local maybe_arch
-  local core_image_exists="false"
-
-  # Parse ARCHS={a,b,c,d} syntax.
-  local array
-  IFS=, read -a array <<< "${ARCHS:1:(-1)}";
-  for maybe_arch in "${array[@]}"; do
-    if [[ -e "$image_location_dir/$maybe_arch/$image_location_name" ]]; then
-      core_image_exists="true"
-      break
-    fi
-  done
-
-  if [[ "$core_image_exists" == "false" ]]; then
+  # If there are no existing core.art, try to find boot.art.
+  # If there is no boot.art then leave it as-is, assumes -Ximage is explicitly used.
+  # Otherwise let dalvikvm give the error message about an invalid image file.
+  if ! check_if_boot_image_file_exists "$image_location_dir" "core.art" && \
+       check_if_boot_image_file_exists "$image_location_dir" "boot.art"; then
     image_location_name="boot.art"
   fi
 
@@ -147,9 +216,15 @@
   echo "$image_location"
 }
 
+# Runs dalvikvm, returns its exit code.
+# (Oat directories are cleaned up in between runs)
 function run_art() {
   local image_location="$(detect_boot_image_location)"
+  local ret
 
+  # First cleanup any left-over 'oat' files from the last time dalvikvm was run.
+  cleanup_oat_directory_for_classpath "$@"
+  # Run dalvikvm.
   verbose_run ANDROID_DATA=$ANDROID_DATA               \
               ANDROID_ROOT=$ANDROID_ROOT               \
               LD_LIBRARY_PATH=$LD_LIBRARY_PATH         \
@@ -160,6 +235,13 @@
               -Xnorelocate                             \
               -Ximage:"$image_location"                \
               "$@"
+  ret=$?
+
+  # Avoid polluting disk with 'oat' files after dalvikvm has finished.
+  cleanup_oat_directory_for_classpath "$@"
+
+  # Forward exit code of dalvikvm.
+  return $ret
 }
 
 while [[ "$1" = "-"* ]]; do
@@ -251,7 +333,7 @@
     # by default.
     ANDROID_DATA="$ANDROID_DATA/local/tmp/android-data$$"
   fi
-  mkdir -p $ANDROID_DATA/dalvik-cache/$ARCHS
+  mkdir -p "$ANDROID_DATA"
   DELETE_ANDROID_DATA="yes"
 fi
 
@@ -264,7 +346,7 @@
   # Create the profile. The runtime expects profiles to be created before
   # execution.
   PROFILE_PATH="$ANDROID_DATA/primary.prof"
-  touch $PROFILE_PATH
+  touch "$PROFILE_PATH"
 
   # Replace the compiler filter with quicken so that we
   # can capture the profile.
@@ -282,13 +364,15 @@
   EXIT_STATUS=$?
 
   if [ $EXIT_STATUS != 0 ]; then
-    cat "$ANDROID_DATA/profile_gen.log"
+    echo "Profile run failed: " >&2
+    cat "$ANDROID_DATA/profile_gen.log" >&2
     clean_android_data
     exit $EXIT_STATUS
   fi
 
-  # Wipe dalvik-cache to prepare it for the next invocation.
-  rm -rf $ANDROID_DATA/dalvik-cache/$ARCHS/*
+  # Wipe dalvik-cache so that a subsequent run_art must regenerate it.
+  # Leave $ANDROID_DATA intact since it contains our profile file.
+  rm -rf "$ANDROID_DATA/dalvik-cache"
 
   # Append arguments so next invocation of run_art uses the profile.
   EXTRA_OPTIONS+=(-Xcompiler-option --profile-file="$PROFILE_PATH")
diff --git a/tools/buildbot-build.sh b/tools/buildbot-build.sh
index 75694c3..4f99ac3 100755
--- a/tools/buildbot-build.sh
+++ b/tools/buildbot-build.sh
@@ -19,6 +19,8 @@
   exit 1
 fi
 
+source build/envsetup.sh >&/dev/null # for get_build_var
+
 # Logic for setting out_dir from build/make/core/envsetup.mk:
 if [[ -z $OUT_DIR ]]; then
   if [[ -z $OUT_DIR_COMMON_BASE ]]; then
@@ -30,10 +32,7 @@
   out_dir=${OUT_DIR}
 fi
 
-using_jack=true
-if [[ $ANDROID_COMPILE_WITH_JACK == false ]]; then
-  using_jack=false
-fi
+using_jack=$(get_build_var ANDROID_COMPILE_WITH_JACK)
 
 java_libraries_dir=${out_dir}/target/common/obj/JAVA_LIBRARIES
 common_targets="vogar core-tests apache-harmony-jdwp-tests-hostdex jsr166-tests mockito-target"
@@ -63,7 +62,7 @@
   fi
 done
 
-if $using_jack; then
+if [[ $using_jack == "true" ]]; then
   common_targets="$common_targets ${out_dir}/host/linux-x86/bin/jack"
 fi
 
diff --git a/tools/cpplint_presubmit.py b/tools/cpplint_presubmit.py
index 4781517..b42a691 100755
--- a/tools/cpplint_presubmit.py
+++ b/tools/cpplint_presubmit.py
@@ -21,7 +21,7 @@
 import subprocess
 import sys
 
-IGNORED_FILES = {"runtime/elf.h", "runtime/openjdkjvmti/include/jvmti.h"}
+IGNORED_FILES = {"runtime/elf.h", "openjdkjvmti/include/jvmti.h"}
 
 INTERESTING_SUFFIXES = {".h", ".cc"}
 
diff --git a/tools/dexfuzz/src/dexfuzz/executors/Executor.java b/tools/dexfuzz/src/dexfuzz/executors/Executor.java
index 074672d..0367a83 100644
--- a/tools/dexfuzz/src/dexfuzz/executors/Executor.java
+++ b/tools/dexfuzz/src/dexfuzz/executors/Executor.java
@@ -114,8 +114,6 @@
 
     commandBuilder.append("--oat-file=output.oat ");
     commandBuilder.append("--android-root=").append(device.getAndroidHostOut()).append(" ");
-    commandBuilder.append("--runtime-arg -classpath ");
-    commandBuilder.append("--runtime-arg ").append(programName).append(" ");
     commandBuilder.append("--dex-file=").append(programName).append(" ");
     commandBuilder.append("--compiler-filter=quicken --runtime-arg -Xnorelocate ");
 
diff --git a/tools/generate_cmake_lists.py b/tools/generate_cmake_lists.py
new file mode 100755
index 0000000..6c3ce08
--- /dev/null
+++ b/tools/generate_cmake_lists.py
@@ -0,0 +1,98 @@
+#!/usr/bin/env python
+#
+# Copyright 2017, The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+"""
+./generate_cmake_lists.py --project-name <project-name> --arch <arch>
+
+- project-name - name of the new project
+- arch - arch type. make generates seperate CMakeLists files for
+         each architecture. To avoid collision in targets, only one of
+         them can be included in the super project.
+
+The primary objective of this file is to generate CMakeLists files
+for CLion setup.
+
+Steps to setup CLion.
+1) Open the generated CMakeList file in CLion as a project.
+2) Change the project root ANDROID_BUILD_TOP.
+(Also, exclude projects that you don't bother about. This will make
+the indexing faster).
+"""
+
+import sys
+import os
+import subprocess
+import argparse
+
+def get_android_build_top():
+  path_to_top = os.environ.get('ANDROID_BUILD_TOP')
+  if not path_to_top:
+    # nothing set. try to guess it based on the relative path of this env.py file.
+    this_file_path = os.path.realpath(__file__)
+    path_to_top = os.path.join(os.path.dirname(this_file_path), '../..')
+    path_to_top = os.path.realpath(path_to_top)
+
+  if not os.path.exists(os.path.join(path_to_top, 'build/envsetup.sh')):
+    print path_to_top
+    raise AssertionError("geneate_cmake_lists.py must be located inside an android source tree")
+
+  return path_to_top
+
+def main():
+  # Parse arguments
+  parser = argparse.ArgumentParser(description="Generate CMakeLists files for ART")
+  parser.add_argument('--project-name', dest="project_name", required=True,
+                      help='name of the project')
+  parser.add_argument('--arch', dest="arch", required=True, help='arch')
+  args = parser.parse_args()
+  project_name = args.project_name
+  arch = args.arch
+
+  # Invoke make to generate CMakeFiles
+  os.environ['SOONG_GEN_CMAKEFILES']='1'
+  os.environ['SOONG_GEN_CMAKEFILES_DEBUG']='1'
+
+  ANDROID_BUILD_TOP = get_android_build_top()
+
+  subprocess.check_output(('make -j64 -C %s') % (ANDROID_BUILD_TOP), shell=True)
+
+  out_art_cmakelists_dir = os.path.join(ANDROID_BUILD_TOP,
+                                        'out/development/ide/clion/art')
+
+  # Prepare a list of directories containing generated CMakeLists files for sub projects.
+  cmake_sub_dirs = set()
+  for root, dirs, files in os.walk(out_art_cmakelists_dir):
+    for name in files:
+      if name == 'CMakeLists.txt':
+        if (os.path.samefile(root, out_art_cmakelists_dir)):
+          continue
+        if arch not in root:
+          continue
+        cmake_sub_dir = cmake_sub_dirs.add(root.replace(out_art_cmakelists_dir,
+                                                        '.'))
+
+  # Generate CMakeLists file.
+  f = open(os.path.join(out_art_cmakelists_dir, 'CMakeLists.txt'), 'w')
+  f.write('cmake_minimum_required(VERSION 3.6)\n')
+  f.write('project(%s)\n' % (project_name))
+
+  for dr in cmake_sub_dirs:
+    f.write('add_subdirectory(%s)\n' % (dr))
+
+
+if __name__ == '__main__':
+  main()
diff --git a/tools/jfuzz/jfuzz.cc b/tools/jfuzz/jfuzz.cc
index 016d708..7990c6c 100644
--- a/tools/jfuzz/jfuzz.cc
+++ b/tools/jfuzz/jfuzz.cc
@@ -55,7 +55,7 @@
  * to preserve the property that a given version of JFuzz yields the same
  * fuzzed program for a deterministic random seed.
  */
-const char* VERSION = "1.3";
+const char* VERSION = "1.4";
 
 /*
  * Maximum number of array dimensions, together with corresponding maximum size
@@ -804,13 +804,6 @@
       return emitAssignment();  // fall back
     }
 
-    // TODO: remove this
-    // The jack bug b/28862040 prevents generating while/do-while loops because otherwise
-    // we get dozens of reports on the same issue per nightly/ run.
-    if (true) {
-      return emitAssignment();
-    }
-
     bool isWhile = random1(2) == 1;
     fputs("{\n", out_);
     indentation_ += 2;
diff --git a/tools/jfuzz/run_jfuzz_test.py b/tools/jfuzz/run_jfuzz_test.py
index 7e72aa1..58bc737 100755
--- a/tools/jfuzz/run_jfuzz_test.py
+++ b/tools/jfuzz/run_jfuzz_test.py
@@ -524,7 +524,9 @@
       jfuzz_args = ['\'-{0}\''.format(arg)
                     for arg in jfuzz_cmd_str.strip().split(' -')][1:]
       wrapped_args = ['--jfuzz_arg={0}'.format(opt) for opt in jfuzz_args]
-      repro_cmd_str = (os.path.basename(__file__) + ' --num_tests 1 ' +
+      repro_cmd_str = (os.path.basename(__file__) +
+                       ' --num_tests=1 ' +
+                       ('--use_dx ' if self._use_dx else '') +
                        ' '.join(wrapped_args))
       comment = 'jfuzz {0}\nReproduce test:\n{1}\nReproduce divergence:\n{2}\n'.format(
           jfuzz_ver, jfuzz_cmd_str, repro_cmd_str)
diff --git a/tools/libcore_gcstress_debug_failures.txt b/tools/libcore_gcstress_debug_failures.txt
index b4c6f2b..d4a0423 100644
--- a/tools/libcore_gcstress_debug_failures.txt
+++ b/tools/libcore_gcstress_debug_failures.txt
@@ -9,7 +9,9 @@
   result: EXEC_FAILED,
   modes: [device],
   names: ["libcore.icu.TransliteratorTest#testAll",
+          "libcore.icu.RelativeDateTimeFormatterTest#test_bug25821045",
           "libcore.java.lang.ref.ReferenceQueueTest#testRemoveWithDelayedResultAndTimeout",
+          "libcore.java.lang.ref.ReferenceQueueTest#testRemoveWithDelayedResultAndNoTimeout",
           "libcore.java.util.TimeZoneTest#testSetDefaultDeadlock",
           "org.apache.harmony.tests.java.util.TimerTest#testThrowingTaskKillsTimerThread"]
 }
diff --git a/tools/run-jdwp-tests.sh b/tools/run-jdwp-tests.sh
index 17c84b4..d2322bb 100755
--- a/tools/run-jdwp-tests.sh
+++ b/tools/run-jdwp-tests.sh
@@ -19,24 +19,19 @@
   exit 1
 fi
 
-if [ -z "$ANDROID_JAVA_TOOLCHAIN" ] ; then
-  source build/envsetup.sh
-  setpaths # include platform prebuilt java, javac, etc in $PATH.
-fi
+source build/envsetup.sh >&/dev/null # for get_build_var, setpaths
+setpaths # include platform prebuilt java, javac, etc in $PATH.
 
 if [ -z "$ANDROID_HOST_OUT" ] ; then
   ANDROID_HOST_OUT=${OUT_DIR-$ANDROID_BUILD_TOP/out}/host/linux-x86
 fi
 
-using_jack=true
-if [[ $ANDROID_COMPILE_WITH_JACK == false ]]; then
-  using_jack=false
-fi
+using_jack=$(get_build_var ANDROID_COMPILE_WITH_JACK)
 
 function jlib_suffix {
   local str=$1
   local suffix="jar"
-  if $using_jack; then
+  if [[ $using_jack == "true" ]]; then
     suffix="jack"
   fi
   echo "$str.$suffix"
@@ -166,7 +161,7 @@
   art_debugee="$art_debugee -verbose:jdwp"
 fi
 
-if $using_jack; then
+if [[ $using_jack == "true" ]]; then
   toolchain_args="--toolchain jack --language JN --jack-arg -g"
 else
   toolchain_args="--toolchain jdk --language CUR"
diff --git a/tools/run-libcore-tests.sh b/tools/run-libcore-tests.sh
index d549098..eecdd2f 100755
--- a/tools/run-libcore-tests.sh
+++ b/tools/run-libcore-tests.sh
@@ -19,10 +19,8 @@
   exit 1
 fi
 
-if [ -z "$ANDROID_JAVA_TOOLCHAIN" ] ; then
-  source build/envsetup.sh
-  setpaths # include platform prebuilt java, javac, etc in $PATH.
-fi
+source build/envsetup.sh >&/dev/null # for get_build_var, setpaths
+setpaths # include platform prebuilt java, javac, etc in $PATH.
 
 if [ -z "$ANDROID_PRODUCT_OUT" ] ; then
   JAVA_LIBRARIES=out/target/common/obj/JAVA_LIBRARIES
@@ -30,16 +28,13 @@
   JAVA_LIBRARIES=${ANDROID_PRODUCT_OUT}/../../common/obj/JAVA_LIBRARIES
 fi
 
-using_jack=true
-if [[ $ANDROID_COMPILE_WITH_JACK == false ]]; then
-  using_jack=false
-fi
+using_jack=$(get_build_var ANDROID_COMPILE_WITH_JACK)
 
 function classes_jar_path {
   local var="$1"
   local suffix="jar"
 
-  if $using_jack; then
+  if [[ $using_jack == "true" ]]; then
     suffix="jack"
   fi
 
@@ -151,7 +146,7 @@
 vogar_args="$vogar_args --timeout 480"
 
 # Switch between using jack or javac+desugar+dx
-if $using_jack; then
+if [[ $using_jack == "true" ]]; then
   vogar_args="$vogar_args --toolchain jack --language JO"
 else
   vogar_args="$vogar_args --toolchain jdk --language CUR"