Merge "Remove oat-target* rules"
diff --git a/compiler/dex/verified_method.cc b/compiler/dex/verified_method.cc
index 188209b..cbca333 100644
--- a/compiler/dex/verified_method.cc
+++ b/compiler/dex/verified_method.cc
@@ -59,49 +59,6 @@
   return std::binary_search(safe_cast_set_.begin(), safe_cast_set_.end(), pc);
 }
 
-bool VerifiedMethod::GenerateDequickenMap(verifier::MethodVerifier* method_verifier) {
-  if (method_verifier->HasFailures()) {
-    return false;
-  }
-  const DexFile::CodeItem* code_item = method_verifier->CodeItem();
-  const uint16_t* insns = code_item->insns_;
-  const Instruction* inst = Instruction::At(insns);
-  const Instruction* end = Instruction::At(insns + code_item->insns_size_in_code_units_);
-  for (; inst < end; inst = inst->Next()) {
-    const bool is_virtual_quick = inst->Opcode() == Instruction::INVOKE_VIRTUAL_QUICK;
-    const bool is_range_quick = inst->Opcode() == Instruction::INVOKE_VIRTUAL_RANGE_QUICK;
-    if (is_virtual_quick || is_range_quick) {
-      uint32_t dex_pc = inst->GetDexPc(insns);
-      verifier::RegisterLine* line = method_verifier->GetRegLine(dex_pc);
-      ArtMethod* method =
-          method_verifier->GetQuickInvokedMethod(inst, line, is_range_quick, true);
-      if (method == nullptr) {
-        // It can be null if the line wasn't verified since it was unreachable.
-        return false;
-      }
-      // The verifier must know what the type of the object was or else we would have gotten a
-      // failure. Put the dex method index in the dequicken map since we need this to get number of
-      // arguments in the compiler.
-      dequicken_map_.Put(dex_pc, DexFileReference(method->GetDexFile(),
-                                                  method->GetDexMethodIndex()));
-    } else if (IsInstructionIGetQuickOrIPutQuick(inst->Opcode())) {
-      uint32_t dex_pc = inst->GetDexPc(insns);
-      verifier::RegisterLine* line = method_verifier->GetRegLine(dex_pc);
-      ArtField* field = method_verifier->GetQuickFieldAccess(inst, line);
-      if (field == nullptr) {
-        // It can be null if the line wasn't verified since it was unreachable.
-        return false;
-      }
-      // The verifier must know what the type of the field was or else we would have gotten a
-      // failure. Put the dex field index in the dequicken map since we need this for lowering
-      // in the compiler.
-      // TODO: Putting a field index in a method reference is gross.
-      dequicken_map_.Put(dex_pc, DexFileReference(field->GetDexFile(), field->GetDexFieldIndex()));
-    }
-  }
-  return true;
-}
-
 void VerifiedMethod::GenerateSafeCastSet(verifier::MethodVerifier* method_verifier) {
   /*
    * Walks over the method code and adds any cast instructions in which
diff --git a/compiler/dex/verified_method.h b/compiler/dex/verified_method.h
index 0530a8c..439e69e 100644
--- a/compiler/dex/verified_method.h
+++ b/compiler/dex/verified_method.h
@@ -39,9 +39,6 @@
   // is better for performance (not just memory usage), especially for large sets.
   typedef std::vector<uint32_t> SafeCastSet;
 
-  // Devirtualization map type maps dex offset to field / method idx.
-  typedef SafeMap<uint32_t, DexFileReference> DequickenMap;
-
   static const VerifiedMethod* Create(verifier::MethodVerifier* method_verifier)
       REQUIRES_SHARED(Locks::mutator_lock_);
   ~VerifiedMethod() = default;
@@ -68,17 +65,10 @@
   }
 
  private:
-  // Generate dequickening map into dequicken_map_. Returns false if there is an error.
-  bool GenerateDequickenMap(verifier::MethodVerifier* method_verifier)
-      REQUIRES_SHARED(Locks::mutator_lock_);
-
   // Generate safe case set into safe_cast_set_.
   void GenerateSafeCastSet(verifier::MethodVerifier* method_verifier)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
-  // Dequicken map is required for compiling quickened byte codes. The quicken maps from
-  // dex PC to dex method index or dex field index based on the instruction.
-  DequickenMap dequicken_map_;
   SafeCastSet safe_cast_set_;
 
   const uint32_t encountered_error_types_;
diff --git a/compiler/image_writer.h b/compiler/image_writer.h
index c537483..cc7df1c 100644
--- a/compiler/image_writer.h
+++ b/compiler/image_writer.h
@@ -27,6 +27,7 @@
 #include <string>
 #include <ostream>
 
+#include "art_method.h"
 #include "base/bit_utils.h"
 #include "base/dchecked_vector.h"
 #include "base/enums.h"
diff --git a/compiler/optimizing/code_generator_arm_vixl.cc b/compiler/optimizing/code_generator_arm_vixl.cc
index f108595..00ad3e3 100644
--- a/compiler/optimizing/code_generator_arm_vixl.cc
+++ b/compiler/optimizing/code_generator_arm_vixl.cc
@@ -5681,13 +5681,13 @@
 void ParallelMoveResolverARMVIXL::Exchange(int mem1, int mem2) {
   // TODO(VIXL32): Double check the performance of this implementation.
   UseScratchRegisterScope temps(GetAssembler()->GetVIXLAssembler());
-  vixl32::Register temp = temps.Acquire();
-  vixl32::SRegister temp_s = temps.AcquireS();
+  vixl32::SRegister temp_1 = temps.AcquireS();
+  vixl32::SRegister temp_2 = temps.AcquireS();
 
-  __ Ldr(temp, MemOperand(sp, mem1));
-  __ Vldr(temp_s, MemOperand(sp, mem2));
-  __ Str(temp, MemOperand(sp, mem2));
-  __ Vstr(temp_s, MemOperand(sp, mem1));
+  __ Vldr(temp_1, MemOperand(sp, mem1));
+  __ Vldr(temp_2, MemOperand(sp, mem2));
+  __ Vstr(temp_1, MemOperand(sp, mem2));
+  __ Vstr(temp_2, MemOperand(sp, mem1));
 }
 
 void ParallelMoveResolverARMVIXL::EmitSwap(size_t index) {
diff --git a/compiler/optimizing/code_generator_mips64.cc b/compiler/optimizing/code_generator_mips64.cc
index 5cf3c24..36690c0 100644
--- a/compiler/optimizing/code_generator_mips64.cc
+++ b/compiler/optimizing/code_generator_mips64.cc
@@ -4517,27 +4517,20 @@
   locations->SetInAt(0, Location::RequiresRegister());
 }
 
-void InstructionCodeGeneratorMIPS64::VisitPackedSwitch(HPackedSwitch* switch_instr) {
-  int32_t lower_bound = switch_instr->GetStartValue();
-  int32_t num_entries = switch_instr->GetNumEntries();
-  LocationSummary* locations = switch_instr->GetLocations();
-  GpuRegister value_reg = locations->InAt(0).AsRegister<GpuRegister>();
-  HBasicBlock* default_block = switch_instr->GetDefaultBlock();
-
+void InstructionCodeGeneratorMIPS64::GenPackedSwitchWithCompares(GpuRegister value_reg,
+                                                                 int32_t lower_bound,
+                                                                 uint32_t num_entries,
+                                                                 HBasicBlock* switch_block,
+                                                                 HBasicBlock* default_block) {
   // Create a set of compare/jumps.
   GpuRegister temp_reg = TMP;
-  if (IsInt<16>(-lower_bound)) {
-    __ Addiu(temp_reg, value_reg, -lower_bound);
-  } else {
-    __ LoadConst32(AT, -lower_bound);
-    __ Addu(temp_reg, value_reg, AT);
-  }
+  __ Addiu32(temp_reg, value_reg, -lower_bound);
   // Jump to default if index is negative
   // Note: We don't check the case that index is positive while value < lower_bound, because in
   // this case, index >= num_entries must be true. So that we can save one branch instruction.
   __ Bltzc(temp_reg, codegen_->GetLabelOf(default_block));
 
-  const ArenaVector<HBasicBlock*>& successors = switch_instr->GetBlock()->GetSuccessors();
+  const ArenaVector<HBasicBlock*>& successors = switch_block->GetSuccessors();
   // Jump to successors[0] if value == lower_bound.
   __ Beqzc(temp_reg, codegen_->GetLabelOf(successors[0]));
   int32_t last_index = 0;
@@ -4555,11 +4548,66 @@
   }
 
   // And the default for any other value.
-  if (!codegen_->GoesToNextBlock(switch_instr->GetBlock(), default_block)) {
+  if (!codegen_->GoesToNextBlock(switch_block, default_block)) {
     __ Bc(codegen_->GetLabelOf(default_block));
   }
 }
 
+void InstructionCodeGeneratorMIPS64::GenTableBasedPackedSwitch(GpuRegister value_reg,
+                                                               int32_t lower_bound,
+                                                               uint32_t num_entries,
+                                                               HBasicBlock* switch_block,
+                                                               HBasicBlock* default_block) {
+  // Create a jump table.
+  std::vector<Mips64Label*> labels(num_entries);
+  const ArenaVector<HBasicBlock*>& successors = switch_block->GetSuccessors();
+  for (uint32_t i = 0; i < num_entries; i++) {
+    labels[i] = codegen_->GetLabelOf(successors[i]);
+  }
+  JumpTable* table = __ CreateJumpTable(std::move(labels));
+
+  // Is the value in range?
+  __ Addiu32(TMP, value_reg, -lower_bound);
+  __ LoadConst32(AT, num_entries);
+  __ Bgeuc(TMP, AT, codegen_->GetLabelOf(default_block));
+
+  // We are in the range of the table.
+  // Load the target address from the jump table, indexing by the value.
+  __ LoadLabelAddress(AT, table->GetLabel());
+  __ Sll(TMP, TMP, 2);
+  __ Daddu(TMP, TMP, AT);
+  __ Lw(TMP, TMP, 0);
+  // Compute the absolute target address by adding the table start address
+  // (the table contains offsets to targets relative to its start).
+  __ Daddu(TMP, TMP, AT);
+  // And jump.
+  __ Jr(TMP);
+  __ Nop();
+}
+
+void InstructionCodeGeneratorMIPS64::VisitPackedSwitch(HPackedSwitch* switch_instr) {
+  int32_t lower_bound = switch_instr->GetStartValue();
+  uint32_t num_entries = switch_instr->GetNumEntries();
+  LocationSummary* locations = switch_instr->GetLocations();
+  GpuRegister value_reg = locations->InAt(0).AsRegister<GpuRegister>();
+  HBasicBlock* switch_block = switch_instr->GetBlock();
+  HBasicBlock* default_block = switch_instr->GetDefaultBlock();
+
+  if (num_entries > kPackedSwitchJumpTableThreshold) {
+    GenTableBasedPackedSwitch(value_reg,
+                              lower_bound,
+                              num_entries,
+                              switch_block,
+                              default_block);
+  } else {
+    GenPackedSwitchWithCompares(value_reg,
+                                lower_bound,
+                                num_entries,
+                                switch_block,
+                                default_block);
+  }
+}
+
 void LocationsBuilderMIPS64::VisitClassTableGet(HClassTableGet*) {
   UNIMPLEMENTED(FATAL) << "ClassTableGet is unimplemented on mips64";
 }
diff --git a/compiler/optimizing/code_generator_mips64.h b/compiler/optimizing/code_generator_mips64.h
index d5811c2..8ac919f 100644
--- a/compiler/optimizing/code_generator_mips64.h
+++ b/compiler/optimizing/code_generator_mips64.h
@@ -217,6 +217,14 @@
 
   Mips64Assembler* GetAssembler() const { return assembler_; }
 
+  // Compare-and-jump packed switch generates approx. 3 + 2.5 * N 32-bit
+  // instructions for N cases.
+  // Table-based packed switch generates approx. 11 32-bit instructions
+  // and N 32-bit data words for N cases.
+  // At N = 6 they come out as 18 and 17 32-bit words respectively.
+  // We switch to the table-based method starting with 7 cases.
+  static constexpr uint32_t kPackedSwitchJumpTableThreshold = 6;
+
  private:
   void GenerateClassInitializationCheck(SlowPathCodeMIPS64* slow_path, GpuRegister class_reg);
   void GenerateMemoryBarrier(MemBarrierKind kind);
@@ -260,6 +268,16 @@
                                   LocationSummary* locations,
                                   Mips64Label* label);
   void HandleGoto(HInstruction* got, HBasicBlock* successor);
+  void GenPackedSwitchWithCompares(GpuRegister value_reg,
+                                   int32_t lower_bound,
+                                   uint32_t num_entries,
+                                   HBasicBlock* switch_block,
+                                   HBasicBlock* default_block);
+  void GenTableBasedPackedSwitch(GpuRegister value_reg,
+                                 int32_t lower_bound,
+                                 uint32_t num_entries,
+                                 HBasicBlock* switch_block,
+                                 HBasicBlock* default_block);
 
   Mips64Assembler* const assembler_;
   CodeGeneratorMIPS64* const codegen_;
diff --git a/compiler/optimizing/codegen_test.cc b/compiler/optimizing/codegen_test.cc
index ac83bd9..879b4ce 100644
--- a/compiler/optimizing/codegen_test.cc
+++ b/compiler/optimizing/codegen_test.cc
@@ -1041,6 +1041,31 @@
   }
 }
 
+#ifdef ART_ENABLE_CODEGEN_arm
+TEST_F(CodegenTest, ARMVIXLParallelMoveResolver) {
+  std::unique_ptr<const ArmInstructionSetFeatures> features(
+      ArmInstructionSetFeatures::FromCppDefines());
+  ArenaPool pool;
+  ArenaAllocator allocator(&pool);
+  HGraph* graph = CreateGraph(&allocator);
+  arm::CodeGeneratorARMVIXL codegen(graph, *features.get(), CompilerOptions());
+
+  codegen.Initialize();
+
+  // This will result in calling EmitSwap -> void ParallelMoveResolverARMVIXL::Exchange(int mem1,
+  // int mem2) which was faulty (before the fix). So previously GPR and FP scratch registers were
+  // used as temps; however GPR scratch register is required for big stack offsets which don't fit
+  // LDR encoding. So the following code is a regression test for that situation.
+  HParallelMove* move = new (graph->GetArena()) HParallelMove(graph->GetArena());
+  move->AddMove(Location::StackSlot(0), Location::StackSlot(8192), Primitive::kPrimInt, nullptr);
+  move->AddMove(Location::StackSlot(8192), Location::StackSlot(0), Primitive::kPrimInt, nullptr);
+  codegen.GetMoveResolver()->EmitNativeCode(move);
+
+  InternalCodeAllocator code_allocator;
+  codegen.Finalize(&code_allocator);
+}
+#endif
+
 #ifdef ART_ENABLE_CODEGEN_mips
 TEST_F(CodegenTest, MipsClobberRA) {
   std::unique_ptr<const MipsInstructionSetFeatures> features_mips(
diff --git a/compiler/optimizing/inliner.cc b/compiler/optimizing/inliner.cc
index d847879..3b83e95 100644
--- a/compiler/optimizing/inliner.cc
+++ b/compiler/optimizing/inliner.cc
@@ -344,6 +344,7 @@
   if (actual_method != nullptr) {
     bool result = TryInlineAndReplace(invoke_instruction,
                                       actual_method,
+                                      ReferenceTypeInfo::CreateInvalid(),
                                       /* do_rtp */ true,
                                       cha_devirtualize);
     if (result && !invoke_instruction->IsInvokeStaticOrDirect()) {
@@ -471,9 +472,10 @@
   HInstruction* receiver = invoke_instruction->InputAt(0);
   HInstruction* cursor = invoke_instruction->GetPrevious();
   HBasicBlock* bb_cursor = invoke_instruction->GetBlock();
-
+  Handle<mirror::Class> handle = handles_->NewHandle(GetMonomorphicType(classes));
   if (!TryInlineAndReplace(invoke_instruction,
                            resolved_method,
+                           ReferenceTypeInfo::Create(handle, /* is_exact */ true),
                            /* do_rtp */ false,
                            /* cha_devirtualize */ false)) {
     return false;
@@ -591,13 +593,13 @@
       break;
     }
     ArtMethod* method = nullptr;
+
+    Handle<mirror::Class> handle = handles_->NewHandle(classes->Get(i));
     if (invoke_instruction->IsInvokeInterface()) {
-      method = classes->Get(i)->FindVirtualMethodForInterface(
-          resolved_method, pointer_size);
+      method = handle->FindVirtualMethodForInterface(resolved_method, pointer_size);
     } else {
       DCHECK(invoke_instruction->IsInvokeVirtual());
-      method = classes->Get(i)->FindVirtualMethodForVirtual(
-          resolved_method, pointer_size);
+      method = handle->FindVirtualMethodForVirtual(resolved_method, pointer_size);
     }
 
     HInstruction* receiver = invoke_instruction->InputAt(0);
@@ -605,10 +607,13 @@
     HBasicBlock* bb_cursor = invoke_instruction->GetBlock();
 
     dex::TypeIndex class_index = FindClassIndexIn(
-        classes->Get(i), caller_dex_file, caller_compilation_unit_.GetDexCache());
+        handle.Get(), caller_dex_file, caller_compilation_unit_.GetDexCache());
     HInstruction* return_replacement = nullptr;
     if (!class_index.IsValid() ||
-        !TryBuildAndInline(invoke_instruction, method, &return_replacement)) {
+        !TryBuildAndInline(invoke_instruction,
+                           method,
+                           ReferenceTypeInfo::Create(handle, /* is_exact */ true),
+                           &return_replacement)) {
       all_targets_inlined = false;
     } else {
       one_target_inlined = true;
@@ -627,7 +632,7 @@
                                            cursor,
                                            bb_cursor,
                                            class_index,
-                                           classes->Get(i),
+                                           handle.Get(),
                                            invoke_instruction,
                                            deoptimize);
       if (deoptimize) {
@@ -792,7 +797,10 @@
   HBasicBlock* bb_cursor = invoke_instruction->GetBlock();
 
   HInstruction* return_replacement = nullptr;
-  if (!TryBuildAndInline(invoke_instruction, actual_method, &return_replacement)) {
+  if (!TryBuildAndInline(invoke_instruction,
+                         actual_method,
+                         ReferenceTypeInfo::CreateInvalid(),
+                         &return_replacement)) {
     return false;
   }
 
@@ -857,13 +865,14 @@
 
 bool HInliner::TryInlineAndReplace(HInvoke* invoke_instruction,
                                    ArtMethod* method,
+                                   ReferenceTypeInfo receiver_type,
                                    bool do_rtp,
                                    bool cha_devirtualize) {
   HInstruction* return_replacement = nullptr;
   uint32_t dex_pc = invoke_instruction->GetDexPc();
   HInstruction* cursor = invoke_instruction->GetPrevious();
   HBasicBlock* bb_cursor = invoke_instruction->GetBlock();
-  if (!TryBuildAndInline(invoke_instruction, method, &return_replacement)) {
+  if (!TryBuildAndInline(invoke_instruction, method, receiver_type, &return_replacement)) {
     if (invoke_instruction->IsInvokeInterface()) {
       // Turn an invoke-interface into an invoke-virtual. An invoke-virtual is always
       // better than an invoke-interface because:
@@ -921,6 +930,7 @@
 
 bool HInliner::TryBuildAndInline(HInvoke* invoke_instruction,
                                  ArtMethod* method,
+                                 ReferenceTypeInfo receiver_type,
                                  HInstruction** return_replacement) {
   if (method->IsProxyMethod()) {
     VLOG(compiler) << "Method " << method->PrettyMethod()
@@ -997,7 +1007,8 @@
     return false;
   }
 
-  if (!TryBuildAndInlineHelper(invoke_instruction, method, same_dex_file, return_replacement)) {
+  if (!TryBuildAndInlineHelper(
+          invoke_instruction, method, receiver_type, same_dex_file, return_replacement)) {
     return false;
   }
 
@@ -1194,8 +1205,10 @@
 
 bool HInliner::TryBuildAndInlineHelper(HInvoke* invoke_instruction,
                                        ArtMethod* resolved_method,
+                                       ReferenceTypeInfo receiver_type,
                                        bool same_dex_file,
                                        HInstruction** return_replacement) {
+  DCHECK(!(resolved_method->IsStatic() && receiver_type.IsValid()));
   ScopedObjectAccess soa(Thread::Current());
   const DexFile::CodeItem* code_item = resolved_method->GetCodeItem();
   const DexFile& callee_dex_file = *resolved_method->GetDexFile();
@@ -1286,12 +1299,13 @@
   }
 
   size_t parameter_index = 0;
+  bool run_rtp = false;
   for (HInstructionIterator instructions(callee_graph->GetEntryBlock()->GetInstructions());
        !instructions.Done();
        instructions.Advance()) {
     HInstruction* current = instructions.Current();
     if (current->IsParameterValue()) {
-      HInstruction* argument = invoke_instruction->InputAt(parameter_index++);
+      HInstruction* argument = invoke_instruction->InputAt(parameter_index);
       if (argument->IsNullConstant()) {
         current->ReplaceWith(callee_graph->GetNullConstant());
       } else if (argument->IsIntConstant()) {
@@ -1305,15 +1319,21 @@
         current->ReplaceWith(
             callee_graph->GetDoubleConstant(argument->AsDoubleConstant()->GetValue()));
       } else if (argument->GetType() == Primitive::kPrimNot) {
-        current->SetReferenceTypeInfo(argument->GetReferenceTypeInfo());
+        if (!resolved_method->IsStatic() && parameter_index == 0 && receiver_type.IsValid()) {
+          run_rtp = true;
+          current->SetReferenceTypeInfo(receiver_type);
+        } else {
+          current->SetReferenceTypeInfo(argument->GetReferenceTypeInfo());
+        }
         current->AsParameterValue()->SetCanBeNull(argument->CanBeNull());
       }
+      ++parameter_index;
     }
   }
 
   // We have replaced formal arguments with actual arguments. If actual types
   // are more specific than the declared ones, run RTP again on the inner graph.
-  if (ArgumentTypesMoreSpecific(invoke_instruction, resolved_method)) {
+  if (run_rtp || ArgumentTypesMoreSpecific(invoke_instruction, resolved_method)) {
     ReferenceTypePropagation(callee_graph,
                              dex_compilation_unit.GetDexCache(),
                              handles_,
@@ -1502,7 +1522,7 @@
 
   ReferenceTypeInfo actual_rti = actual_obj->GetReferenceTypeInfo();
   return (actual_rti.IsExact() && !declared_rti.IsExact()) ||
-         declared_rti.IsStrictSupertypeOf(actual_rti);
+          declared_rti.IsStrictSupertypeOf(actual_rti);
 }
 
 ReferenceTypeInfo HInliner::GetClassRTI(mirror::Class* klass) {
diff --git a/compiler/optimizing/inliner.h b/compiler/optimizing/inliner.h
index 0c64362..4c0b990 100644
--- a/compiler/optimizing/inliner.h
+++ b/compiler/optimizing/inliner.h
@@ -66,17 +66,20 @@
   // a CHA guard needs to be added for the inlining.
   bool TryInlineAndReplace(HInvoke* invoke_instruction,
                            ArtMethod* resolved_method,
+                           ReferenceTypeInfo receiver_type,
                            bool do_rtp,
                            bool cha_devirtualize)
     REQUIRES_SHARED(Locks::mutator_lock_);
 
   bool TryBuildAndInline(HInvoke* invoke_instruction,
                          ArtMethod* resolved_method,
+                         ReferenceTypeInfo receiver_type,
                          HInstruction** return_replacement)
     REQUIRES_SHARED(Locks::mutator_lock_);
 
   bool TryBuildAndInlineHelper(HInvoke* invoke_instruction,
                                ArtMethod* resolved_method,
+                               ReferenceTypeInfo receiver_type,
                                bool same_dex_file,
                                HInstruction** return_replacement);
 
diff --git a/compiler/optimizing/intrinsics_mips.cc b/compiler/optimizing/intrinsics_mips.cc
index 1fb90e5..e9c6615 100644
--- a/compiler/optimizing/intrinsics_mips.cc
+++ b/compiler/optimizing/intrinsics_mips.cc
@@ -2466,6 +2466,94 @@
   __ Bind(&done);
 }
 
+// void java.lang.String.getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin)
+void IntrinsicLocationsBuilderMIPS::VisitStringGetCharsNoCheck(HInvoke* invoke) {
+  LocationSummary* locations = new (arena_) LocationSummary(invoke,
+                                                            LocationSummary::kCallOnMainOnly,
+                                                            kIntrinsified);
+  locations->SetInAt(0, Location::RequiresRegister());
+  locations->SetInAt(1, Location::RequiresRegister());
+  locations->SetInAt(2, Location::RequiresRegister());
+  locations->SetInAt(3, Location::RequiresRegister());
+  locations->SetInAt(4, Location::RequiresRegister());
+
+  // We will call memcpy() to do the actual work. Allocate the temporary
+  // registers to use the correct input registers, and output register.
+  // memcpy() uses the normal MIPS calling convention.
+  InvokeRuntimeCallingConvention calling_convention;
+
+  locations->AddTemp(Location::RegisterLocation(calling_convention.GetRegisterAt(0)));
+  locations->AddTemp(Location::RegisterLocation(calling_convention.GetRegisterAt(1)));
+  locations->AddTemp(Location::RegisterLocation(calling_convention.GetRegisterAt(2)));
+
+  Location outLocation = calling_convention.GetReturnLocation(Primitive::kPrimInt);
+  locations->AddTemp(Location::RegisterLocation(outLocation.AsRegister<Register>()));
+}
+
+void IntrinsicCodeGeneratorMIPS::VisitStringGetCharsNoCheck(HInvoke* invoke) {
+  MipsAssembler* assembler = GetAssembler();
+  LocationSummary* locations = invoke->GetLocations();
+
+  // Check assumption that sizeof(Char) is 2 (used in scaling below).
+  const size_t char_size = Primitive::ComponentSize(Primitive::kPrimChar);
+  DCHECK_EQ(char_size, 2u);
+  const size_t char_shift = Primitive::ComponentSizeShift(Primitive::kPrimChar);
+
+  Register srcObj = locations->InAt(0).AsRegister<Register>();
+  Register srcBegin = locations->InAt(1).AsRegister<Register>();
+  Register srcEnd = locations->InAt(2).AsRegister<Register>();
+  Register dstObj = locations->InAt(3).AsRegister<Register>();
+  Register dstBegin = locations->InAt(4).AsRegister<Register>();
+
+  Register dstPtr = locations->GetTemp(0).AsRegister<Register>();
+  DCHECK_EQ(dstPtr, A0);
+  Register srcPtr = locations->GetTemp(1).AsRegister<Register>();
+  DCHECK_EQ(srcPtr, A1);
+  Register numChrs = locations->GetTemp(2).AsRegister<Register>();
+  DCHECK_EQ(numChrs, A2);
+
+  Register dstReturn = locations->GetTemp(3).AsRegister<Register>();
+  DCHECK_EQ(dstReturn, V0);
+
+  MipsLabel done;
+
+  // Location of data in char array buffer.
+  const uint32_t data_offset = mirror::Array::DataOffset(char_size).Uint32Value();
+
+  // Get offset of value field within a string object.
+  const int32_t value_offset = mirror::String::ValueOffset().Int32Value();
+
+  __ Beq(srcEnd, srcBegin, &done);  // No characters to move.
+
+  // Calculate number of characters to be copied.
+  __ Subu(numChrs, srcEnd, srcBegin);
+
+  // Calculate destination address.
+  __ Addiu(dstPtr, dstObj, data_offset);
+  if (IsR6()) {
+    __ Lsa(dstPtr, dstBegin, dstPtr, char_shift);
+  } else {
+    __ Sll(AT, dstBegin, char_shift);
+    __ Addu(dstPtr, dstPtr, AT);
+  }
+
+  // Calculate source address.
+  __ Addiu(srcPtr, srcObj, value_offset);
+  if (IsR6()) {
+    __ Lsa(srcPtr, srcBegin, srcPtr, char_shift);
+  } else {
+    __ Sll(AT, srcBegin, char_shift);
+    __ Addu(srcPtr, srcPtr, AT);
+  }
+
+  // Calculate number of bytes to copy from number of characters.
+  __ Sll(numChrs, numChrs, char_shift);
+
+  codegen_->InvokeRuntime(kQuickMemcpy, invoke, invoke->GetDexPc(), nullptr);
+
+  __ Bind(&done);
+}
+
 // Unimplemented intrinsics.
 
 UNIMPLEMENTED_INTRINSIC(MIPS, MathCeil)
@@ -2475,7 +2563,6 @@
 UNIMPLEMENTED_INTRINSIC(MIPS, UnsafeCASLong)
 
 UNIMPLEMENTED_INTRINSIC(MIPS, ReferenceGetReferent)
-UNIMPLEMENTED_INTRINSIC(MIPS, StringGetCharsNoCheck)
 UNIMPLEMENTED_INTRINSIC(MIPS, SystemArrayCopyChar)
 UNIMPLEMENTED_INTRINSIC(MIPS, SystemArrayCopy)
 
diff --git a/compiler/optimizing/nodes.h b/compiler/optimizing/nodes.h
index afa17ce..8c64d25 100644
--- a/compiler/optimizing/nodes.h
+++ b/compiler/optimizing/nodes.h
@@ -5762,7 +5762,6 @@
       : HInstruction(SideEffectsForArchRuntimeCalls(), dex_pc),
         special_input_(HUserRecord<HInstruction*>(current_method)),
         string_index_(string_index) {
-    SetPackedFlag<kFlagIsInDexCache>(false);
     SetPackedField<LoadKindField>(LoadKind::kDexCacheViaMethod);
     load_data_.dex_file_ = &dex_file;
   }
@@ -5789,7 +5788,6 @@
   const DexFile& GetDexFile() const;
 
   dex::StringIndex GetStringIndex() const {
-    DCHECK(HasStringReference(GetLoadKind()) || /* For slow paths. */ !IsInDexCache());
     return string_index_;
   }
 
@@ -5814,7 +5812,7 @@
         load_kind == LoadKind::kJitTableAddress) {
       return false;
     }
-    return !IsInDexCache();
+    return true;
   }
 
   bool NeedsDexCacheOfDeclaringClass() const OVERRIDE {
@@ -5828,15 +5826,6 @@
     return SideEffects::CanTriggerGC();
   }
 
-  bool IsInDexCache() const { return GetPackedFlag<kFlagIsInDexCache>(); }
-
-  void MarkInDexCache() {
-    SetPackedFlag<kFlagIsInDexCache>(true);
-    DCHECK(!NeedsEnvironment());
-    RemoveEnvironment();
-    SetSideEffects(SideEffects::None());
-  }
-
   void AddSpecialInput(HInstruction* special_input);
 
   using HInstruction::GetInputRecords;  // Keep the const version visible.
@@ -5852,8 +5841,7 @@
   DECLARE_INSTRUCTION(LoadString);
 
  private:
-  static constexpr size_t kFlagIsInDexCache = kNumberOfGenericPackedBits;
-  static constexpr size_t kFieldLoadKind = kFlagIsInDexCache + 1;
+  static constexpr size_t kFieldLoadKind = kNumberOfGenericPackedBits;
   static constexpr size_t kFieldLoadKindSize =
       MinimumBitsToStore(static_cast<size_t>(LoadKind::kLast));
   static constexpr size_t kNumberOfLoadStringPackedBits = kFieldLoadKind + kFieldLoadKindSize;
diff --git a/compiler/optimizing/sharpening.cc b/compiler/optimizing/sharpening.cc
index 9fdeccf..ca26c30 100644
--- a/compiler/optimizing/sharpening.cc
+++ b/compiler/optimizing/sharpening.cc
@@ -270,7 +270,6 @@
 
 void HSharpening::ProcessLoadString(HLoadString* load_string) {
   DCHECK_EQ(load_string->GetLoadKind(), HLoadString::LoadKind::kDexCacheViaMethod);
-  DCHECK(!load_string->IsInDexCache());
 
   const DexFile& dex_file = load_string->GetDexFile();
   dex::StringIndex string_index = load_string->GetStringIndex();
diff --git a/compiler/utils/mips/assembler_mips.cc b/compiler/utils/mips/assembler_mips.cc
index 3dcad6a..5e83e82 100644
--- a/compiler/utils/mips/assembler_mips.cc
+++ b/compiler/utils/mips/assembler_mips.cc
@@ -635,6 +635,13 @@
   DsFsmInstrRrr(EmitR(0x1f, rt, rd, static_cast<Register>(pos + size - 1), pos, 0x04), rd, rd, rt);
 }
 
+void MipsAssembler::Lsa(Register rd, Register rs, Register rt, int saPlusOne) {
+  CHECK(IsR6());
+  CHECK(1 <= saPlusOne && saPlusOne <= 4) << saPlusOne;
+  int sa = saPlusOne - 1;
+  DsFsmInstrRrr(EmitR(0x0, rs, rt, rd, sa, 0x05), rd, rs, rt);
+}
+
 void MipsAssembler::Lb(Register rt, Register rs, uint16_t imm16) {
   DsFsmInstrRrr(EmitI(0x20, rs, rt, imm16), rt, rs, rs);
 }
diff --git a/compiler/utils/mips/assembler_mips.h b/compiler/utils/mips/assembler_mips.h
index 800dc5f..2fca185 100644
--- a/compiler/utils/mips/assembler_mips.h
+++ b/compiler/utils/mips/assembler_mips.h
@@ -262,6 +262,7 @@
   void Srav(Register rd, Register rt, Register rs);
   void Ext(Register rd, Register rt, int pos, int size);  // R2+
   void Ins(Register rd, Register rt, int pos, int size);  // R2+
+  void Lsa(Register rd, Register rs, Register rt, int saPlusOne);  // R6
 
   void Lb(Register rt, Register rs, uint16_t imm16);
   void Lh(Register rt, Register rs, uint16_t imm16);
diff --git a/compiler/utils/mips/assembler_mips32r6_test.cc b/compiler/utils/mips/assembler_mips32r6_test.cc
index a52f519..30667ef 100644
--- a/compiler/utils/mips/assembler_mips32r6_test.cc
+++ b/compiler/utils/mips/assembler_mips32r6_test.cc
@@ -319,6 +319,14 @@
   DriverStr(RepeatRR(&mips::MipsAssembler::Bitswap, "bitswap ${reg1}, ${reg2}"), "bitswap");
 }
 
+TEST_F(AssemblerMIPS32r6Test, Lsa) {
+  DriverStr(RepeatRRRIb(&mips::MipsAssembler::Lsa,
+                        2,
+                        "lsa ${reg1}, ${reg2}, ${reg3}, {imm}",
+                        1),
+            "lsa");
+}
+
 TEST_F(AssemblerMIPS32r6Test, Seleqz) {
   DriverStr(RepeatRRR(&mips::MipsAssembler::Seleqz, "seleqz ${reg1}, ${reg2}, ${reg3}"),
             "seleqz");
diff --git a/compiler/utils/mips64/assembler_mips64.cc b/compiler/utils/mips64/assembler_mips64.cc
index 5906a71..998f2c7 100644
--- a/compiler/utils/mips64/assembler_mips64.cc
+++ b/compiler/utils/mips64/assembler_mips64.cc
@@ -35,12 +35,14 @@
   for (auto& exception_block : exception_blocks_) {
     EmitExceptionPoll(&exception_block);
   }
+  ReserveJumpTableSpace();
   EmitLiterals();
   PromoteBranches();
 }
 
 void Mips64Assembler::FinalizeInstructions(const MemoryRegion& region) {
   EmitBranches();
+  EmitJumpTables();
   Assembler::FinalizeInstructions(region);
   PatchCFI();
 }
@@ -482,6 +484,10 @@
   EmitI(0xf, static_cast<GpuRegister>(0), rt, imm16);
 }
 
+void Mips64Assembler::Aui(GpuRegister rt, GpuRegister rs, uint16_t imm16) {
+  EmitI(0xf, rs, rt, imm16);
+}
+
 void Mips64Assembler::Dahi(GpuRegister rs, uint16_t imm16) {
   EmitI(1, rs, static_cast<GpuRegister>(6), imm16);
 }
@@ -1081,6 +1087,20 @@
   TemplateLoadConst64(this, rd, value);
 }
 
+void Mips64Assembler::Addiu32(GpuRegister rt, GpuRegister rs, int32_t value) {
+  if (IsInt<16>(value)) {
+    Addiu(rt, rs, value);
+  } else {
+    int16_t high = High16Bits(value);
+    int16_t low = Low16Bits(value);
+    high += (low < 0) ? 1 : 0;  // Account for sign extension in addiu.
+    Aui(rt, rs, high);
+    if (low != 0) {
+      Addiu(rt, rt, low);
+    }
+  }
+}
+
 void Mips64Assembler::Daddiu64(GpuRegister rt, GpuRegister rs, int64_t value, GpuRegister rtmp) {
   if (IsInt<16>(value)) {
     Daddiu(rt, rs, value);
@@ -1653,6 +1673,67 @@
   FinalizeLabeledBranch(label);
 }
 
+JumpTable* Mips64Assembler::CreateJumpTable(std::vector<Mips64Label*>&& labels) {
+  jump_tables_.emplace_back(std::move(labels));
+  JumpTable* table = &jump_tables_.back();
+  DCHECK(!table->GetLabel()->IsBound());
+  return table;
+}
+
+void Mips64Assembler::ReserveJumpTableSpace() {
+  if (!jump_tables_.empty()) {
+    for (JumpTable& table : jump_tables_) {
+      Mips64Label* label = table.GetLabel();
+      Bind(label);
+
+      // Bulk ensure capacity, as this may be large.
+      size_t orig_size = buffer_.Size();
+      size_t required_capacity = orig_size + table.GetSize();
+      if (required_capacity > buffer_.Capacity()) {
+        buffer_.ExtendCapacity(required_capacity);
+      }
+#ifndef NDEBUG
+      buffer_.has_ensured_capacity_ = true;
+#endif
+
+      // Fill the space with dummy data as the data is not final
+      // until the branches have been promoted. And we shouldn't
+      // be moving uninitialized data during branch promotion.
+      for (size_t cnt = table.GetData().size(), i = 0; i < cnt; i++) {
+        buffer_.Emit<uint32_t>(0x1abe1234u);
+      }
+
+#ifndef NDEBUG
+      buffer_.has_ensured_capacity_ = false;
+#endif
+    }
+  }
+}
+
+void Mips64Assembler::EmitJumpTables() {
+  if (!jump_tables_.empty()) {
+    CHECK(!overwriting_);
+    // Switch from appending instructions at the end of the buffer to overwriting
+    // existing instructions (here, jump tables) in the buffer.
+    overwriting_ = true;
+
+    for (JumpTable& table : jump_tables_) {
+      Mips64Label* table_label = table.GetLabel();
+      uint32_t start = GetLabelLocation(table_label);
+      overwrite_location_ = start;
+
+      for (Mips64Label* target : table.GetData()) {
+        CHECK_EQ(buffer_.Load<uint32_t>(overwrite_location_), 0x1abe1234u);
+        // The table will contain target addresses relative to the table start.
+        uint32_t offset = GetLabelLocation(target) - start;
+        Emit(offset);
+      }
+    }
+
+    overwriting_ = false;
+  }
+}
+
 void Mips64Assembler::EmitLiterals() {
   if (!literals_.empty()) {
     for (Literal& literal : literals_) {
diff --git a/compiler/utils/mips64/assembler_mips64.h b/compiler/utils/mips64/assembler_mips64.h
index 7ef5ab0..a0a1db6 100644
--- a/compiler/utils/mips64/assembler_mips64.h
+++ b/compiler/utils/mips64/assembler_mips64.h
@@ -357,6 +357,36 @@
   DISALLOW_COPY_AND_ASSIGN(Literal);
 };
 
+// Jump table: table of labels emitted after the code and before the literals. Similar to literals.
+class JumpTable {
+ public:
+  explicit JumpTable(std::vector<Mips64Label*>&& labels)
+      : label_(), labels_(std::move(labels)) {
+  }
+
+  size_t GetSize() const {
+    return labels_.size() * sizeof(uint32_t);
+  }
+
+  const std::vector<Mips64Label*>& GetData() const {
+    return labels_;
+  }
+
+  Mips64Label* GetLabel() {
+    return &label_;
+  }
+
+  const Mips64Label* GetLabel() const {
+    return &label_;
+  }
+
+ private:
+  Mips64Label label_;
+  std::vector<Mips64Label*> labels_;
+
+  DISALLOW_COPY_AND_ASSIGN(JumpTable);
+};
+
 // Slowpath entered when Thread::Current()->_exception is non-null.
 class Mips64ExceptionSlowPath {
  public:
@@ -388,6 +418,7 @@
         overwrite_location_(0),
         literals_(arena->Adapter(kArenaAllocAssembler)),
         long_literals_(arena->Adapter(kArenaAllocAssembler)),
+        jump_tables_(arena->Adapter(kArenaAllocAssembler)),
         last_position_adjustment_(0),
         last_old_position_(0),
         last_branch_id_(0) {
@@ -480,6 +511,7 @@
   void Lwupc(GpuRegister rs, uint32_t imm19);  // MIPS64
   void Ldpc(GpuRegister rs, uint32_t imm18);  // MIPS64
   void Lui(GpuRegister rt, uint16_t imm16);
+  void Aui(GpuRegister rt, GpuRegister rs, uint16_t imm16);
   void Dahi(GpuRegister rs, uint16_t imm16);  // MIPS64
   void Dati(GpuRegister rs, uint16_t imm16);  // MIPS64
   void Sync(uint32_t stype);
@@ -619,6 +651,7 @@
   // This function is only used for testing purposes.
   void RecordLoadConst64Path(int value);
 
+  void Addiu32(GpuRegister rt, GpuRegister rs, int32_t value);
   void Daddiu64(GpuRegister rt, GpuRegister rs, int64_t value, GpuRegister rtmp = AT);  // MIPS64
 
   void Bind(Label* label) OVERRIDE {
@@ -676,6 +709,12 @@
   // Load literal using PC-relative loads.
   void LoadLiteral(GpuRegister dest_reg, LoadOperandType load_type, Literal* literal);
 
+  // Create a jump table for the given labels that will be emitted when finalizing.
+  // When the table is emitted, offsets will be relative to the location of the table.
+  // The table location is determined by the location of its label (the label precedes
+  // the table data) and should be loaded using LoadLabelAddress().
+  JumpTable* CreateJumpTable(std::vector<Mips64Label*>&& labels);
+
   void Bc(Mips64Label* label);
   void Balc(Mips64Label* label);
   void Bltc(GpuRegister rs, GpuRegister rt, Mips64Label* label);
@@ -1050,6 +1089,8 @@
   const Branch* GetBranch(uint32_t branch_id) const;
 
   void EmitLiterals();
+  void ReserveJumpTableSpace();
+  void EmitJumpTables();
   void PromoteBranches();
   void EmitBranch(Branch* branch);
   void EmitBranches();
@@ -1073,6 +1114,9 @@
   ArenaDeque<Literal> literals_;
   ArenaDeque<Literal> long_literals_;  // 64-bit literals separated for alignment reasons.
 
+  // Jump table list.
+  ArenaDeque<JumpTable> jump_tables_;
+
   // Data for AdjustedPosition(), see the description there.
   uint32_t last_position_adjustment_;
   uint32_t last_old_position_;
diff --git a/compiler/utils/mips64/assembler_mips64_test.cc b/compiler/utils/mips64/assembler_mips64_test.cc
index 564559f..f2cbebb 100644
--- a/compiler/utils/mips64/assembler_mips64_test.cc
+++ b/compiler/utils/mips64/assembler_mips64_test.cc
@@ -1904,9 +1904,9 @@
   DriverStr(expected, "StoreFpuToOffset");
 }
 
-///////////////////////
-// Loading Constants //
-///////////////////////
+//////////////////////////////
+// Loading/adding Constants //
+//////////////////////////////
 
 TEST_F(AssemblerMIPS64Test, LoadConst32) {
   // IsUint<16>(value)
@@ -1949,6 +1949,31 @@
   DriverStr(expected, "LoadConst32");
 }
 
+TEST_F(AssemblerMIPS64Test, Addiu32) {
+  __ Addiu32(mips64::A1, mips64::A2, -0x8000);
+  __ Addiu32(mips64::A1, mips64::A2, +0);
+  __ Addiu32(mips64::A1, mips64::A2, +0x7FFF);
+  __ Addiu32(mips64::A1, mips64::A2, -0x8001);
+  __ Addiu32(mips64::A1, mips64::A2, +0x8000);
+  __ Addiu32(mips64::A1, mips64::A2, -0x10000);
+  __ Addiu32(mips64::A1, mips64::A2, +0x10000);
+  __ Addiu32(mips64::A1, mips64::A2, +0x12345678);
+
+  const char* expected =
+      "addiu $a1, $a2, -0x8000\n"
+      "addiu $a1, $a2, 0\n"
+      "addiu $a1, $a2, 0x7FFF\n"
+      "aui $a1, $a2, 0xFFFF\n"
+      "addiu $a1, $a1, 0x7FFF\n"
+      "aui $a1, $a2, 1\n"
+      "addiu $a1, $a1, -0x8000\n"
+      "aui $a1, $a2, 0xFFFF\n"
+      "aui $a1, $a2, 1\n"
+      "aui $a1, $a2, 0x1234\n"
+      "addiu $a1, $a1, 0x5678\n";
+  DriverStr(expected, "Addiu32");
+}
+
 static uint64_t SignExtend16To64(uint16_t n) {
   return static_cast<int16_t>(n);
 }
diff --git a/compiler/verifier_deps_test.cc b/compiler/verifier_deps_test.cc
index 85ae61f..4f06a91 100644
--- a/compiler/verifier_deps_test.cc
+++ b/compiler/verifier_deps_test.cc
@@ -524,7 +524,7 @@
                                          /* src */ "LMyThreadSet;",
                                          /* is_strict */ true,
                                          /* is_assignable */ true));
-  ASSERT_TRUE(HasAssignable("Ljava/util/Collection;", "LMyThreadSet;", true));
+  ASSERT_TRUE(HasAssignable("Ljava/util/Collection;", "Ljava/util/Set;", true));
 }
 
 TEST_F(VerifierDepsTest, Assignable_BothArrays_Resolved) {
@@ -539,26 +539,6 @@
   ASSERT_TRUE(HasAssignable("Ljava/util/TimeZone;", "Ljava/util/SimpleTimeZone;", true));
 }
 
-// We test that VerifierDeps does not try to optimize by storing assignability
-// of the component types. This is due to the fact that the component type may
-// be an erroneous class, even though the array type has resolved status.
-
-TEST_F(VerifierDepsTest, Assignable_ArrayToInterface1) {
-  ASSERT_TRUE(TestAssignabilityRecording(/* dst */ "Ljava/io/Serializable;",
-                                         /* src */ "[Ljava/util/TimeZone;",
-                                         /* is_strict */ true,
-                                         /* is_assignable */ true));
-  ASSERT_TRUE(HasAssignable("Ljava/io/Serializable;", "[Ljava/util/TimeZone;", true));
-}
-
-TEST_F(VerifierDepsTest, Assignable_ArrayToInterface2) {
-  ASSERT_TRUE(TestAssignabilityRecording(/* dst */ "Ljava/io/Serializable;",
-                                         /* src */ "[LMyThreadSet;",
-                                         /* is_strict */ true,
-                                         /* is_assignable */ true));
-  ASSERT_TRUE(HasAssignable("Ljava/io/Serializable;", "[LMyThreadSet;", true));
-}
-
 TEST_F(VerifierDepsTest, NotAssignable_BothInBoot) {
   ASSERT_TRUE(TestAssignabilityRecording(/* dst */ "Ljava/lang/Exception;",
                                          /* src */ "Ljava/util/SimpleTimeZone;",
@@ -1083,7 +1063,7 @@
 TEST_F(VerifierDepsTest, InvokeSuper_ThisAssignable) {
   ASSERT_TRUE(VerifyMethod("InvokeSuper_ThisAssignable"));
   ASSERT_TRUE(HasClass("Ljava/lang/Runnable;", true, "public abstract interface"));
-  ASSERT_TRUE(HasAssignable("Ljava/lang/Runnable;", "LMain;", true));
+  ASSERT_TRUE(HasAssignable("Ljava/lang/Runnable;", "Ljava/lang/Thread;", true));
   ASSERT_TRUE(HasMethod("interface",
                         "Ljava/lang/Runnable;",
                         "run",
@@ -1101,6 +1081,16 @@
       "virtual", "Ljava/lang/Integer;", "intValue", "()I", true, "public", "Ljava/lang/Integer;"));
 }
 
+TEST_F(VerifierDepsTest, ArgumentType_ResolvedReferenceArray) {
+  ASSERT_TRUE(VerifyMethod("ArgumentType_ResolvedReferenceArray"));
+  ASSERT_TRUE(HasClass("[Ljava/lang/Thread;", true, "public final abstract"));
+}
+
+TEST_F(VerifierDepsTest, NewArray_Resolved) {
+  ASSERT_TRUE(VerifyMethod("NewArray_Resolved"));
+  ASSERT_TRUE(HasClass("[Ljava/lang/IllegalStateException;", true, "public final abstract"));
+}
+
 TEST_F(VerifierDepsTest, EncodeDecode) {
   VerifyDexFile();
 
diff --git a/runtime/art_method-inl.h b/runtime/art_method-inl.h
index ef03bb3..96976d9 100644
--- a/runtime/art_method-inl.h
+++ b/runtime/art_method-inl.h
@@ -134,8 +134,7 @@
   // NOTE: Unchecked, i.e. not throwing AIOOB. We don't even know the length here
   // without accessing the DexCache and we don't want to do that in release build.
   DCHECK_LT(method_index,
-            GetInterfaceMethodIfProxy(pointer_size)->GetDeclaringClass()
-                ->GetDexCache()->NumResolvedMethods());
+            GetInterfaceMethodIfProxy(pointer_size)->GetDexCache()->NumResolvedMethods());
   ArtMethod* method = mirror::DexCache::GetElementPtrSize(GetDexCacheResolvedMethods(pointer_size),
                                                           method_index,
                                                           pointer_size);
@@ -154,8 +153,7 @@
   // NOTE: Unchecked, i.e. not throwing AIOOB. We don't even know the length here
   // without accessing the DexCache and we don't want to do that in release build.
   DCHECK_LT(method_index,
-            GetInterfaceMethodIfProxy(pointer_size)->GetDeclaringClass()
-                ->GetDexCache()->NumResolvedMethods());
+            GetInterfaceMethodIfProxy(pointer_size)->GetDexCache()->NumResolvedMethods());
   DCHECK(new_method == nullptr || new_method->GetDeclaringClass() != nullptr);
   mirror::DexCache::SetElementPtrSize(GetDexCacheResolvedMethods(pointer_size),
                                       method_index,
@@ -186,8 +184,7 @@
 inline mirror::Class* ArtMethod::GetDexCacheResolvedType(dex::TypeIndex type_index,
                                                          PointerSize pointer_size) {
   if (kWithCheck) {
-    mirror::DexCache* dex_cache =
-        GetInterfaceMethodIfProxy(pointer_size)->GetDeclaringClass()->GetDexCache();
+    mirror::DexCache* dex_cache = GetInterfaceMethodIfProxy(pointer_size)->GetDexCache();
     if (UNLIKELY(type_index.index_ >= dex_cache->NumResolvedTypes())) {
       ThrowArrayIndexOutOfBoundsException(type_index.index_, dex_cache->NumResolvedTypes());
       return nullptr;
@@ -333,7 +330,7 @@
 }
 
 inline const DexFile::CodeItem* ArtMethod::GetCodeItem() {
-  return GetDeclaringClass()->GetDexFile().GetCodeItem(GetCodeItemOffset());
+  return GetDexFile()->GetCodeItem(GetCodeItemOffset());
 }
 
 inline bool ArtMethod::IsResolvedTypeIdx(dex::TypeIndex type_idx, PointerSize pointer_size) {
@@ -398,11 +395,11 @@
 }
 
 inline mirror::DexCache* ArtMethod::GetDexCache() {
-  DCHECK(!IsProxyMethod());
-  if (UNLIKELY(IsObsolete())) {
-    return GetObsoleteDexCache();
-  } else {
+  if (LIKELY(!IsObsolete())) {
     return GetDeclaringClass()->GetDexCache();
+  } else {
+    DCHECK(!IsProxyMethod());
+    return GetObsoleteDexCache();
   }
 }
 
diff --git a/runtime/art_method.h b/runtime/art_method.h
index 3bc6f5d..b38508b 100644
--- a/runtime/art_method.h
+++ b/runtime/art_method.h
@@ -27,6 +27,7 @@
 #include "invoke_type.h"
 #include "method_reference.h"
 #include "modifiers.h"
+#include "mirror/dex_cache.h"
 #include "mirror/object.h"
 #include "obj_ptr.h"
 #include "read_barrier_option.h"
@@ -220,6 +221,12 @@
     return !IsIntrinsic() && (GetAccessFlags() & kAccObsoleteMethod) != 0;
   }
 
+  void SetIsObsolete() {
+    // TODO We should really support redefining intrinsic if possible.
+    DCHECK(!IsIntrinsic());
+    SetAccessFlags(GetAccessFlags() | kAccObsoleteMethod);
+  }
+
   template <ReadBarrierOption kReadBarrierOption = kWithReadBarrier>
   bool IsNative() {
     return (GetAccessFlags<kReadBarrierOption>() & kAccNative) != 0;
@@ -250,7 +257,6 @@
   }
 
   void SetSkipAccessChecks() {
-    DCHECK(!SkipAccessChecks());
     AddAccessFlags(kAccSkipAccessChecks);
   }
 
@@ -326,6 +332,7 @@
   ALWAYS_INLINE ArtMethod* GetDexCacheResolvedMethod(uint16_t method_index,
                                                      PointerSize pointer_size)
       REQUIRES_SHARED(Locks::mutator_lock_);
+
   ALWAYS_INLINE void SetDexCacheResolvedMethod(uint16_t method_index,
                                                ArtMethod* new_method,
                                                PointerSize pointer_size)
diff --git a/runtime/base/hash_set.h b/runtime/base/hash_set.h
index f24a862..a22efcf 100644
--- a/runtime/base/hash_set.h
+++ b/runtime/base/hash_set.h
@@ -672,6 +672,8 @@
   T* data_;  // Backing storage.
   double min_load_factor_;
   double max_load_factor_;
+
+  ART_FRIEND_TEST(InternTableTest, CrossHash);
 };
 
 template <class T, class EmptyFn, class HashFn, class Pred, class Alloc>
diff --git a/runtime/base/mutex.h b/runtime/base/mutex.h
index 255ad71..2adeb8c 100644
--- a/runtime/base/mutex.h
+++ b/runtime/base/mutex.h
@@ -60,6 +60,7 @@
   kUnexpectedSignalLock,
   kThreadSuspendCountLock,
   kAbortLock,
+  kJdwpAdbStateLock,
   kJdwpSocketLock,
   kRegionSpaceRegionLock,
   kRosAllocGlobalLock,
diff --git a/runtime/class_linker-inl.h b/runtime/class_linker-inl.h
index a11257f..5fc5f1a 100644
--- a/runtime/class_linker-inl.h
+++ b/runtime/class_linker-inl.h
@@ -81,9 +81,6 @@
     Handle<mirror::DexCache> dex_cache(hs.NewHandle(declaring_class->GetDexCache()));
     const DexFile& dex_file = *dex_cache->GetDexFile();
     string = ResolveString(dex_file, string_idx, dex_cache);
-    if (string != nullptr) {
-      DCHECK_EQ(dex_cache->GetResolvedString(string_idx), string);
-    }
   }
   return string.Ptr();
 }
@@ -192,20 +189,15 @@
   return dex_cache->GetResolvedField(field_idx, image_pointer_size_);
 }
 
-inline ArtField* ClassLinker::GetResolvedField(uint32_t field_idx,
-                                               ObjPtr<mirror::Class> field_declaring_class) {
-  return GetResolvedField(field_idx, MakeObjPtr(field_declaring_class->GetDexCache()));
-}
-
 inline ArtField* ClassLinker::ResolveField(uint32_t field_idx,
                                            ArtMethod* referrer,
                                            bool is_static) {
   Thread::PoisonObjectPointersIfDebug();
   ObjPtr<mirror::Class> declaring_class = referrer->GetDeclaringClass();
-  ArtField* resolved_field = GetResolvedField(field_idx, declaring_class);
+  ArtField* resolved_field = GetResolvedField(field_idx, referrer->GetDexCache());
   if (UNLIKELY(resolved_field == nullptr)) {
     StackHandleScope<2> hs(Thread::Current());
-    Handle<mirror::DexCache> dex_cache(hs.NewHandle(declaring_class->GetDexCache()));
+    Handle<mirror::DexCache> dex_cache(hs.NewHandle(referrer->GetDexCache()));
     Handle<mirror::ClassLoader> class_loader(hs.NewHandle(declaring_class->GetClassLoader()));
     const DexFile& dex_file = *dex_cache->GetDexFile();
     resolved_field = ResolveField(dex_file, field_idx, dex_cache, class_loader, is_static);
diff --git a/runtime/class_linker.h b/runtime/class_linker.h
index 9b25303..6ef882a 100644
--- a/runtime/class_linker.h
+++ b/runtime/class_linker.h
@@ -327,8 +327,6 @@
       REQUIRES_SHARED(Locks::mutator_lock_)
       REQUIRES(!Locks::dex_lock_, !Roles::uninterruptible_);
 
-  ArtField* GetResolvedField(uint32_t field_idx, ObjPtr<mirror::Class> field_declaring_class)
-      REQUIRES_SHARED(Locks::mutator_lock_);
   ArtField* GetResolvedField(uint32_t field_idx, ObjPtr<mirror::DexCache> dex_cache)
       REQUIRES_SHARED(Locks::mutator_lock_);
   ArtField* ResolveField(uint32_t field_idx, ArtMethod* referrer, bool is_static)
diff --git a/runtime/common_dex_operations.h b/runtime/common_dex_operations.h
new file mode 100644
index 0000000..6693eef
--- /dev/null
+++ b/runtime/common_dex_operations.h
@@ -0,0 +1,199 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ART_RUNTIME_COMMON_DEX_OPERATIONS_H_
+#define ART_RUNTIME_COMMON_DEX_OPERATIONS_H_
+
+#include "art_field.h"
+#include "art_method.h"
+#include "class_linker.h"
+#include "interpreter/unstarted_runtime.h"
+#include "runtime.h"
+#include "stack.h"
+#include "thread.h"
+
+namespace art {
+
+namespace interpreter {
+  void ArtInterpreterToInterpreterBridge(Thread* self,
+                                        const DexFile::CodeItem* code_item,
+                                        ShadowFrame* shadow_frame,
+                                        JValue* result)
+     REQUIRES_SHARED(Locks::mutator_lock_);
+
+  void ArtInterpreterToCompiledCodeBridge(Thread* self,
+                                          ArtMethod* caller,
+                                          const DexFile::CodeItem* code_item,
+                                          ShadowFrame* shadow_frame,
+                                          JValue* result);
+}  // namespace interpreter
+
+inline void PerformCall(Thread* self,
+                        const DexFile::CodeItem* code_item,
+                        ArtMethod* caller_method,
+                        const size_t first_dest_reg,
+                        ShadowFrame* callee_frame,
+                        JValue* result)
+    REQUIRES_SHARED(Locks::mutator_lock_) {
+  if (LIKELY(Runtime::Current()->IsStarted())) {
+    ArtMethod* target = callee_frame->GetMethod();
+    if (ClassLinker::ShouldUseInterpreterEntrypoint(
+        target,
+        target->GetEntryPointFromQuickCompiledCode())) {
+      interpreter::ArtInterpreterToInterpreterBridge(self, code_item, callee_frame, result);
+    } else {
+      interpreter::ArtInterpreterToCompiledCodeBridge(
+          self, caller_method, code_item, callee_frame, result);
+    }
+  } else {
+    interpreter::UnstartedRuntime::Invoke(self, code_item, callee_frame, result, first_dest_reg);
+  }
+}
+
+template<Primitive::Type field_type>
+static ALWAYS_INLINE void DoFieldGetCommon(Thread* self,
+                                           const ShadowFrame& shadow_frame,
+                                           ObjPtr<mirror::Object> obj,
+                                           ArtField* field,
+                                           JValue* result)
+    REQUIRES_SHARED(Locks::mutator_lock_) {
+  field->GetDeclaringClass()->AssertInitializedOrInitializingInThread(self);
+
+  // Report this field access to instrumentation if needed.
+  instrumentation::Instrumentation* instrumentation = Runtime::Current()->GetInstrumentation();
+  if (UNLIKELY(instrumentation->HasFieldReadListeners())) {
+    StackHandleScope<1> hs(self);
+    // Wrap in handle wrapper in case the listener does thread suspension.
+    HandleWrapperObjPtr<mirror::Object> h(hs.NewHandleWrapper(&obj));
+    ObjPtr<mirror::Object> this_object;
+    if (!field->IsStatic()) {
+      this_object = obj;
+    }
+    instrumentation->FieldReadEvent(self,
+                                    this_object.Ptr(),
+                                    shadow_frame.GetMethod(),
+                                    shadow_frame.GetDexPC(),
+                                    field);
+  }
+
+  switch (field_type) {
+    case Primitive::kPrimBoolean:
+      result->SetZ(field->GetBoolean(obj));
+      break;
+    case Primitive::kPrimByte:
+      result->SetB(field->GetByte(obj));
+      break;
+    case Primitive::kPrimChar:
+      result->SetC(field->GetChar(obj));
+      break;
+    case Primitive::kPrimShort:
+      result->SetS(field->GetShort(obj));
+      break;
+    case Primitive::kPrimInt:
+      result->SetI(field->GetInt(obj));
+      break;
+    case Primitive::kPrimLong:
+      result->SetJ(field->GetLong(obj));
+      break;
+    case Primitive::kPrimNot:
+      result->SetL(field->GetObject(obj));
+      break;
+    case Primitive::kPrimVoid:
+      LOG(FATAL) << "Unreachable " << field_type;
+      break;
+  }
+}
+
+template<Primitive::Type field_type, bool do_assignability_check, bool transaction_active>
+ALWAYS_INLINE bool DoFieldPutCommon(Thread* self,
+                                    const ShadowFrame& shadow_frame,
+                                    ObjPtr<mirror::Object> obj,
+                                    ArtField* field,
+                                    const JValue& value)
+    REQUIRES_SHARED(Locks::mutator_lock_) {
+  field->GetDeclaringClass()->AssertInitializedOrInitializingInThread(self);
+
+  // Report this field access to instrumentation if needed. Since we only have the offset of
+  // the field from the base of the object, we need to look for it first.
+  instrumentation::Instrumentation* instrumentation = Runtime::Current()->GetInstrumentation();
+  if (UNLIKELY(instrumentation->HasFieldWriteListeners())) {
+    StackHandleScope<1> hs(self);
+    // Wrap in handle wrapper in case the listener does thread suspension.
+    HandleWrapperObjPtr<mirror::Object> h(hs.NewHandleWrapper(&obj));
+    ObjPtr<mirror::Object> this_object = field->IsStatic() ? nullptr : obj;
+    instrumentation->FieldWriteEvent(self, this_object.Ptr(),
+                                     shadow_frame.GetMethod(),
+                                     shadow_frame.GetDexPC(),
+                                     field,
+                                     value);
+  }
+
+  switch (field_type) {
+    case Primitive::kPrimBoolean:
+      field->SetBoolean<transaction_active>(obj, value.GetZ());
+      break;
+    case Primitive::kPrimByte:
+      field->SetByte<transaction_active>(obj, value.GetB());
+      break;
+    case Primitive::kPrimChar:
+      field->SetChar<transaction_active>(obj, value.GetC());
+      break;
+    case Primitive::kPrimShort:
+      field->SetShort<transaction_active>(obj, value.GetS());
+      break;
+    case Primitive::kPrimInt:
+      field->SetInt<transaction_active>(obj, value.GetI());
+      break;
+    case Primitive::kPrimLong:
+      field->SetLong<transaction_active>(obj, value.GetJ());
+      break;
+    case Primitive::kPrimNot: {
+      ObjPtr<mirror::Object> reg = value.GetL();
+      if (do_assignability_check && reg != nullptr) {
+        // FieldHelper::GetType can resolve classes, use a handle wrapper which will restore the
+        // object in the destructor.
+        ObjPtr<mirror::Class> field_class;
+        {
+          StackHandleScope<2> hs(self);
+          HandleWrapperObjPtr<mirror::Object> h_reg(hs.NewHandleWrapper(&reg));
+          HandleWrapperObjPtr<mirror::Object> h_obj(hs.NewHandleWrapper(&obj));
+          field_class = field->GetType<true>();
+        }
+        if (!reg->VerifierInstanceOf(field_class.Ptr())) {
+          // This should never happen.
+          std::string temp1, temp2, temp3;
+          self->ThrowNewExceptionF("Ljava/lang/InternalError;",
+                                   "Put '%s' that is not instance of field '%s' in '%s'",
+                                   reg->GetClass()->GetDescriptor(&temp1),
+                                   field_class->GetDescriptor(&temp2),
+                                   field->GetDeclaringClass()->GetDescriptor(&temp3));
+          return false;
+        }
+      }
+      field->SetObj<transaction_active>(obj, reg);
+      break;
+    }
+    case Primitive::kPrimVoid: {
+      LOG(FATAL) << "Unreachable " << field_type;
+      break;
+    }
+  }
+  return true;
+}
+
+}  // namespace art
+
+#endif  // ART_RUNTIME_COMMON_DEX_OPERATIONS_H_
diff --git a/runtime/entrypoints/entrypoint_utils-inl.h b/runtime/entrypoints/entrypoint_utils-inl.h
index f6eeffc..14c9c21 100644
--- a/runtime/entrypoints/entrypoint_utils-inl.h
+++ b/runtime/entrypoints/entrypoint_utils-inl.h
@@ -563,7 +563,7 @@
       HandleWrapperObjPtr<mirror::Object> h_this(hs2.NewHandleWrapper(this_object));
       Handle<mirror::Class> h_referring_class(hs2.NewHandle(referrer->GetDeclaringClass()));
       const dex::TypeIndex method_type_idx =
-          h_referring_class->GetDexFile().GetMethodId(method_idx).class_idx_;
+          referrer->GetDexFile()->GetMethodId(method_idx).class_idx_;
       mirror::Class* method_reference_class = class_linker->ResolveType(method_type_idx, referrer);
       if (UNLIKELY(method_reference_class == nullptr)) {
         // Bad type idx.
@@ -673,8 +673,7 @@
                                size_t expected_size) {
   ScopedAssertNoThreadSuspension ants(__FUNCTION__);
   ArtField* resolved_field =
-      referrer->GetDeclaringClass()->GetDexCache()->GetResolvedField(field_idx,
-                                                                     kRuntimePointerSize);
+      referrer->GetDexCache()->GetResolvedField(field_idx, kRuntimePointerSize);
   if (UNLIKELY(resolved_field == nullptr)) {
     return nullptr;
   }
@@ -733,7 +732,7 @@
   }
   mirror::Class* referring_class = referrer->GetDeclaringClass();
   ArtMethod* resolved_method =
-      referring_class->GetDexCache()->GetResolvedMethod(method_idx, kRuntimePointerSize);
+      referrer->GetDexCache()->GetResolvedMethod(method_idx, kRuntimePointerSize);
   if (UNLIKELY(resolved_method == nullptr)) {
     return nullptr;
   }
@@ -759,9 +758,9 @@
   } else if (type == kSuper) {
     // TODO This lookup is rather slow.
     dex::TypeIndex method_type_idx =
-        referring_class->GetDexFile().GetMethodId(method_idx).class_idx_;
+        referrer->GetDexFile()->GetMethodId(method_idx).class_idx_;
     mirror::Class* method_reference_class =
-        referring_class->GetDexCache()->GetResolvedType(method_type_idx);
+        referrer->GetDexCache()->GetResolvedType(method_type_idx);
     if (method_reference_class == nullptr) {
       // Need to do full type resolution...
       return nullptr;
diff --git a/runtime/gc/collector/concurrent_copying.cc b/runtime/gc/collector/concurrent_copying.cc
index b889913..e1117e6 100644
--- a/runtime/gc/collector/concurrent_copying.cc
+++ b/runtime/gc/collector/concurrent_copying.cc
@@ -58,7 +58,7 @@
                                      bool measure_read_barrier_slow_path)
     : GarbageCollector(heap,
                        name_prefix + (name_prefix.empty() ? "" : " ") +
-                       "concurrent copying + mark sweep"),
+                       "concurrent copying"),
       region_space_(nullptr), gc_barrier_(new Barrier(0)),
       gc_mark_stack_(accounting::ObjectStack::Create("concurrent copying gc mark stack",
                                                      kDefaultGcMarkStackSize,
@@ -832,7 +832,6 @@
       static constexpr uint64_t kEmptyCheckpointTimeoutMs = 600 * 1000;  // 10 minutes.
       bool timed_out = barrier->Increment(self, barrier_count, kEmptyCheckpointTimeoutMs);
       if (timed_out) {
-        Runtime* runtime = Runtime::Current();
         std::ostringstream ss;
         ss << "Empty checkpoint timeout\n";
         ss << "Barrier count " << barrier->GetCount(self) << "\n";
@@ -843,8 +842,30 @@
         ss << "\n";
         Locks::mutator_lock_->Dump(ss);
         ss << "\n";
-        runtime->GetThreadList()->Dump(ss);
-        LOG(FATAL) << ss.str();
+        LOG(FATAL_WITHOUT_ABORT) << ss.str();
+        // Some threads in 'runnable_thread_ids' are probably stuck. Try to dump their stacks.
+        // Avoid using ThreadList::Dump() initially because it is likely to get stuck as well.
+        {
+          ReaderMutexLock mu0(self, *Locks::mutator_lock_);
+          MutexLock mu1(self, *Locks::thread_list_lock_);
+          for (Thread* thread : thread_list->GetList()) {
+            uint32_t tid = thread->GetThreadId();
+            bool is_in_runnable_thread_ids =
+                std::find(runnable_thread_ids.begin(), runnable_thread_ids.end(), tid) !=
+                runnable_thread_ids.end();
+            if (is_in_runnable_thread_ids &&
+                thread->ReadFlag(kEmptyCheckpointRequest)) {
+              // Found a runnable thread that hasn't responded to the empty checkpoint request.
+              // Assume it's stuck and safe to dump its stack.
+              thread->Dump(LOG_STREAM(FATAL_WITHOUT_ABORT));
+            }
+          }
+        }
+        LOG(FATAL_WITHOUT_ABORT)
+            << "Dumped runnable threads that haven't responded to empty checkpoint.";
+        // Now use ThreadList::Dump() to dump more threads, noting it may get stuck.
+        thread_list->Dump(LOG_STREAM(FATAL_WITHOUT_ABORT));
+        LOG(FATAL) << "Dumped all threads.";
       }
     } else {
       barrier->Increment(self, barrier_count);
diff --git a/runtime/gc/collector/semi_space.cc b/runtime/gc/collector/semi_space.cc
index a815b83..f2aa5a7 100644
--- a/runtime/gc/collector/semi_space.cc
+++ b/runtime/gc/collector/semi_space.cc
@@ -89,7 +89,7 @@
 
 SemiSpace::SemiSpace(Heap* heap, bool generational, const std::string& name_prefix)
     : GarbageCollector(heap,
-                       name_prefix + (name_prefix.empty() ? "" : " ") + "marksweep + semispace"),
+                       name_prefix + (name_prefix.empty() ? "" : " ") + "semispace"),
       mark_stack_(nullptr),
       is_large_object_space_immune_(false),
       to_space_(nullptr),
diff --git a/runtime/instrumentation.cc b/runtime/instrumentation.cc
index 03ef962..4ea1130 100644
--- a/runtime/instrumentation.cc
+++ b/runtime/instrumentation.cc
@@ -557,8 +557,10 @@
 }
 
 Instrumentation::InstrumentationLevel Instrumentation::GetCurrentInstrumentationLevel() const {
-  if (interpreter_stubs_installed_) {
+  if (interpreter_stubs_installed_ && interpret_only_) {
     return InstrumentationLevel::kInstrumentWithInterpreter;
+  } else if (interpreter_stubs_installed_) {
+    return InstrumentationLevel::kInstrumentWithInterpreterAndJit;
   } else if (entry_exit_stubs_installed_) {
     return InstrumentationLevel::kInstrumentWithInstrumentationStubs;
   } else {
@@ -566,6 +568,14 @@
   }
 }
 
+bool Instrumentation::RequiresInstrumentationInstallation(InstrumentationLevel new_level) const {
+  // We need to reinstall instrumentation if we go to a different level or if the current level is
+  // kInstrumentWithInterpreterAndJit since that level does not force all code to always use the
+  // interpreter and so we might have started running optimized code again.
+  return new_level == InstrumentationLevel::kInstrumentWithInterpreterAndJit ||
+      GetCurrentInstrumentationLevel() != new_level;
+}
+
 void Instrumentation::ConfigureStubs(const char* key, InstrumentationLevel desired_level) {
   // Store the instrumentation level for this key or remove it.
   if (desired_level == InstrumentationLevel::kInstrumentNothing) {
@@ -585,8 +595,7 @@
   interpret_only_ = (requested_level == InstrumentationLevel::kInstrumentWithInterpreter) ||
                     forced_interpret_only_;
 
-  InstrumentationLevel current_level = GetCurrentInstrumentationLevel();
-  if (requested_level == current_level) {
+  if (!RequiresInstrumentationInstallation(requested_level)) {
     // We're already set.
     return;
   }
@@ -595,7 +604,7 @@
   Locks::mutator_lock_->AssertExclusiveHeld(self);
   Locks::thread_list_lock_->AssertNotHeld(self);
   if (requested_level > InstrumentationLevel::kInstrumentNothing) {
-    if (requested_level == InstrumentationLevel::kInstrumentWithInterpreter) {
+    if (requested_level >= InstrumentationLevel::kInstrumentWithInterpreterAndJit) {
       interpreter_stubs_installed_ = true;
       entry_exit_stubs_installed_ = true;
     } else {
@@ -842,7 +851,8 @@
 void Instrumentation::DisableDeoptimization(const char* key) {
   CHECK_EQ(deoptimization_enabled_, true);
   // If we deoptimized everything, undo it.
-  if (interpreter_stubs_installed_) {
+  InstrumentationLevel level = GetCurrentInstrumentationLevel();
+  if (level == InstrumentationLevel::kInstrumentWithInterpreter) {
     UndeoptimizeEverything(key);
   }
   // Undeoptimized selected methods.
@@ -869,6 +879,14 @@
   return !deoptimization_enabled_ && !interpreter_stubs_installed_;
 }
 
+// TODO we don't check deoptimization_enabled_ because currently there isn't really any support for
+// multiple users of instrumentation. Since this is just a temporary state anyway pending work to
+// ensure that the current_method doesn't get kept across suspend points this should be okay.
+// TODO Remove once b/33630159 is resolved.
+void Instrumentation::ReJitEverything(const char* key) {
+  ConfigureStubs(key, InstrumentationLevel::kInstrumentWithInterpreterAndJit);
+}
+
 void Instrumentation::DeoptimizeEverything(const char* key) {
   CHECK(deoptimization_enabled_);
   ConfigureStubs(key, InstrumentationLevel::kInstrumentWithInterpreter);
diff --git a/runtime/instrumentation.h b/runtime/instrumentation.h
index 1e5fcf2..05c0aaa 100644
--- a/runtime/instrumentation.h
+++ b/runtime/instrumentation.h
@@ -133,6 +133,9 @@
   enum class InstrumentationLevel {
     kInstrumentNothing,                   // execute without instrumentation
     kInstrumentWithInstrumentationStubs,  // execute with instrumentation entry/exit stubs
+    kInstrumentWithInterpreterAndJit,     // execute with interpreter initially and later the JIT
+                                          // (if it is enabled). This level is special in that it
+                                          // always requires re-instrumentation.
     kInstrumentWithInterpreter            // execute with interpreter
   };
 
@@ -163,6 +166,13 @@
   }
   bool ShouldNotifyMethodEnterExitEvents() const REQUIRES_SHARED(Locks::mutator_lock_);
 
+  // Executes everything with the interpreter/jit (if available).
+  void ReJitEverything(const char* key)
+      REQUIRES(Locks::mutator_lock_, Roles::uninterruptible_)
+      REQUIRES(!Locks::thread_list_lock_,
+               !Locks::classlinker_classes_lock_,
+               !deoptimized_methods_lock_);
+
   // Executes everything with interpreter.
   void DeoptimizeEverything(const char* key)
       REQUIRES(Locks::mutator_lock_, Roles::uninterruptible_)
@@ -432,9 +442,13 @@
     return alloc_entrypoints_instrumented_;
   }
 
- private:
   InstrumentationLevel GetCurrentInstrumentationLevel() const;
 
+ private:
+  // Returns true if moving to the given instrumentation level requires the installation of stubs.
+  // False otherwise.
+  bool RequiresInstrumentationInstallation(InstrumentationLevel new_level) const;
+
   // Does the job of installing or removing instrumentation code within methods.
   // In order to support multiple clients using instrumentation at the same time,
   // the caller must pass a unique key (a string) identifying it so we remind which
diff --git a/runtime/intern_table.cc b/runtime/intern_table.cc
index 9c05d3c..3e19146 100644
--- a/runtime/intern_table.cc
+++ b/runtime/intern_table.cc
@@ -319,7 +319,9 @@
   if (kIsDebugBuild) {
     Locks::mutator_lock_->AssertSharedHeld(Thread::Current());
   }
-  return static_cast<size_t>(root.Read<kWithoutReadBarrier>()->GetHashCode());
+  // An additional cast to prevent undesired sign extension.
+  return static_cast<size_t>(
+      static_cast<uint32_t>(root.Read<kWithoutReadBarrier>()->GetHashCode()));
 }
 
 bool InternTable::StringHashEquals::operator()(const GcRoot<mirror::String>& a,
diff --git a/runtime/intern_table.h b/runtime/intern_table.h
index f661d9f..68454fb 100644
--- a/runtime/intern_table.h
+++ b/runtime/intern_table.h
@@ -163,7 +163,11 @@
         NO_THREAD_SAFETY_ANALYSIS;
 
     // Utf8String can be used for lookup.
-    std::size_t operator()(const Utf8String& key) const { return key.GetHash(); }
+    std::size_t operator()(const Utf8String& key) const {
+      // A cast to prevent undesired sign extension.
+      return static_cast<uint32_t>(key.GetHash());
+    }
+
     bool operator()(const GcRoot<mirror::String>& a, const Utf8String& b) const
         NO_THREAD_SAFETY_ANALYSIS;
   };
@@ -217,6 +221,8 @@
     // We call AddNewTable when we create the zygote to reduce private dirty pages caused by
     // modifying the zygote intern table. The back of table is modified when strings are interned.
     std::vector<UnorderedSet> tables_;
+
+    ART_FRIEND_TEST(InternTableTest, CrossHash);
   };
 
   // Insert if non null, otherwise return null. Must be called holding the mutator lock.
@@ -276,6 +282,7 @@
   gc::WeakRootState weak_root_state_ GUARDED_BY(Locks::intern_table_lock_);
 
   friend class Transaction;
+  ART_FRIEND_TEST(InternTableTest, CrossHash);
   DISALLOW_COPY_AND_ASSIGN(InternTable);
 };
 
diff --git a/runtime/intern_table_test.cc b/runtime/intern_table_test.cc
index b91d946..3991d65 100644
--- a/runtime/intern_table_test.cc
+++ b/runtime/intern_table_test.cc
@@ -16,6 +16,7 @@
 
 #include "intern_table.h"
 
+#include "base/hash_set.h"
 #include "common_runtime_test.h"
 #include "mirror/object.h"
 #include "handle_scope-inl.h"
@@ -62,6 +63,25 @@
   EXPECT_EQ(2U, t.Size());
 }
 
+// Check if table indexes match on 64 and 32 bit machines.
+// This is done by ensuring hash values are the same on every machine and limited to 32-bit wide.
+// Otherwise cross compilation can cause a table to be filled on host using one indexing algorithm
+// and later on a device with different sizeof(size_t) can use another indexing algorithm.
+// Thus the table may provide wrong data.
+TEST_F(InternTableTest, CrossHash) {
+  ScopedObjectAccess soa(Thread::Current());
+  InternTable t;
+
+  // A string that has a negative hash value.
+  GcRoot<mirror::String> str(mirror::String::AllocFromModifiedUtf8(soa.Self(), "00000000"));
+
+  MutexLock mu(Thread::Current(), *Locks::intern_table_lock_);
+  for (InternTable::Table::UnorderedSet& table : t.strong_interns_.tables_) {
+    // The negative hash value shall be 32-bit wide on every host.
+    ASSERT_TRUE(IsUint<32>(table.hashfn_(str)));
+  }
+}
+
 class TestPredicate : public IsMarkedVisitor {
  public:
   mirror::Object* IsMarked(mirror::Object* s) OVERRIDE REQUIRES_SHARED(Locks::mutator_lock_) {
diff --git a/runtime/interpreter/interpreter_common.cc b/runtime/interpreter/interpreter_common.cc
index a09e71b..ca26207 100644
--- a/runtime/interpreter/interpreter_common.cc
+++ b/runtime/interpreter/interpreter_common.cc
@@ -32,7 +32,6 @@
 #include "reflection.h"
 #include "reflection-inl.h"
 #include "stack.h"
-#include "unstarted_runtime.h"
 #include "verifier/method_verifier.h"
 #include "well_known_classes.h"
 
@@ -43,60 +42,6 @@
   ThrowNullPointerExceptionFromDexPC();
 }
 
-template<Primitive::Type field_type>
-static ALWAYS_INLINE void DoFieldGetCommon(Thread* self,
-                                           const ShadowFrame& shadow_frame,
-                                           ObjPtr<mirror::Object>& obj,
-                                           ArtField* field,
-                                           JValue* result)
-    REQUIRES_SHARED(Locks::mutator_lock_) {
-  field->GetDeclaringClass()->AssertInitializedOrInitializingInThread(self);
-
-  // Report this field access to instrumentation if needed.
-  instrumentation::Instrumentation* instrumentation = Runtime::Current()->GetInstrumentation();
-  if (UNLIKELY(instrumentation->HasFieldReadListeners())) {
-    StackHandleScope<1> hs(self);
-    // Wrap in handle wrapper in case the listener does thread suspension.
-    HandleWrapperObjPtr<mirror::Object> h(hs.NewHandleWrapper(&obj));
-    ObjPtr<mirror::Object> this_object;
-    if (!field->IsStatic()) {
-      this_object = obj;
-    }
-    instrumentation->FieldReadEvent(self,
-                                    this_object.Ptr(),
-                                    shadow_frame.GetMethod(),
-                                    shadow_frame.GetDexPC(),
-                                    field);
-  }
-
-  switch (field_type) {
-    case Primitive::kPrimBoolean:
-      result->SetZ(field->GetBoolean(obj));
-      break;
-    case Primitive::kPrimByte:
-      result->SetB(field->GetByte(obj));
-      break;
-    case Primitive::kPrimChar:
-      result->SetC(field->GetChar(obj));
-      break;
-    case Primitive::kPrimShort:
-      result->SetS(field->GetShort(obj));
-      break;
-    case Primitive::kPrimInt:
-      result->SetI(field->GetInt(obj));
-      break;
-    case Primitive::kPrimLong:
-      result->SetJ(field->GetLong(obj));
-      break;
-    case Primitive::kPrimNot:
-      result->SetL(field->GetObject(obj));
-      break;
-    default:
-      LOG(FATAL) << "Unreachable: " << field_type;
-      UNREACHABLE();
-  }
-}
-
 template<FindFieldType find_type, Primitive::Type field_type, bool do_access_check>
 bool DoFieldGet(Thread* self, ShadowFrame& shadow_frame, const Instruction* inst,
                 uint16_t inst_data) {
@@ -184,48 +129,6 @@
 #undef EXPLICIT_DO_FIELD_GET_ALL_TEMPLATE_DECL
 #undef EXPLICIT_DO_FIELD_GET_TEMPLATE_DECL
 
-// Helper for getters in invoke-polymorphic.
-inline static void DoFieldGetForInvokePolymorphic(Thread* self,
-                                                  const ShadowFrame& shadow_frame,
-                                                  ObjPtr<mirror::Object>& obj,
-                                                  ArtField* field,
-                                                  Primitive::Type field_type,
-                                                  JValue* result)
-    REQUIRES_SHARED(Locks::mutator_lock_) {
-  switch (field_type) {
-    case Primitive::kPrimBoolean:
-      DoFieldGetCommon<Primitive::kPrimBoolean>(self, shadow_frame, obj, field, result);
-      break;
-    case Primitive::kPrimByte:
-      DoFieldGetCommon<Primitive::kPrimByte>(self, shadow_frame, obj, field, result);
-      break;
-    case Primitive::kPrimChar:
-      DoFieldGetCommon<Primitive::kPrimChar>(self, shadow_frame, obj, field, result);
-      break;
-    case Primitive::kPrimShort:
-      DoFieldGetCommon<Primitive::kPrimShort>(self, shadow_frame, obj, field, result);
-      break;
-    case Primitive::kPrimInt:
-      DoFieldGetCommon<Primitive::kPrimInt>(self, shadow_frame, obj, field, result);
-      break;
-    case Primitive::kPrimLong:
-      DoFieldGetCommon<Primitive::kPrimLong>(self, shadow_frame, obj, field, result);
-      break;
-    case Primitive::kPrimFloat:
-      DoFieldGetCommon<Primitive::kPrimInt>(self, shadow_frame, obj, field, result);
-      break;
-    case Primitive::kPrimDouble:
-      DoFieldGetCommon<Primitive::kPrimLong>(self, shadow_frame, obj, field, result);
-      break;
-    case Primitive::kPrimNot:
-      DoFieldGetCommon<Primitive::kPrimNot>(self, shadow_frame, obj, field, result);
-      break;
-    case Primitive::kPrimVoid:
-      LOG(FATAL) << "Unreachable: " << field_type;
-      UNREACHABLE();
-  }
-}
-
 // Handles iget-quick, iget-wide-quick and iget-object-quick instructions.
 // Returns true on success, otherwise throws an exception and returns false.
 template<Primitive::Type field_type>
@@ -300,42 +203,6 @@
 EXPLICIT_DO_IGET_QUICK_TEMPLATE_DECL(Primitive::kPrimNot);      // iget-object-quick.
 #undef EXPLICIT_DO_IGET_QUICK_TEMPLATE_DECL
 
-static JValue GetFieldValue(const ShadowFrame& shadow_frame,
-                            Primitive::Type field_type,
-                            uint32_t vreg)
-    REQUIRES_SHARED(Locks::mutator_lock_) {
-  JValue field_value;
-  switch (field_type) {
-    case Primitive::kPrimBoolean:
-      field_value.SetZ(static_cast<uint8_t>(shadow_frame.GetVReg(vreg)));
-      break;
-    case Primitive::kPrimByte:
-      field_value.SetB(static_cast<int8_t>(shadow_frame.GetVReg(vreg)));
-      break;
-    case Primitive::kPrimChar:
-      field_value.SetC(static_cast<uint16_t>(shadow_frame.GetVReg(vreg)));
-      break;
-    case Primitive::kPrimShort:
-      field_value.SetS(static_cast<int16_t>(shadow_frame.GetVReg(vreg)));
-      break;
-    case Primitive::kPrimInt:
-    case Primitive::kPrimFloat:
-      field_value.SetI(shadow_frame.GetVReg(vreg));
-      break;
-    case Primitive::kPrimLong:
-    case Primitive::kPrimDouble:
-      field_value.SetJ(shadow_frame.GetVRegLong(vreg));
-      break;
-    case Primitive::kPrimNot:
-      field_value.SetL(shadow_frame.GetVRegReference(vreg));
-      break;
-    case Primitive::kPrimVoid:
-      LOG(FATAL) << "Unreachable: " << field_type;
-      UNREACHABLE();
-  }
-  return field_value;
-}
-
 template<Primitive::Type field_type>
 static JValue GetFieldValue(const ShadowFrame& shadow_frame, uint32_t vreg)
     REQUIRES_SHARED(Locks::mutator_lock_) {
@@ -369,82 +236,6 @@
   return field_value;
 }
 
-template<Primitive::Type field_type, bool do_assignability_check, bool transaction_active>
-static inline bool DoFieldPutCommon(Thread* self,
-                                    const ShadowFrame& shadow_frame,
-                                    ObjPtr<mirror::Object>& obj,
-                                    ArtField* f,
-                                    const JValue& value)
-    REQUIRES_SHARED(Locks::mutator_lock_) {
-  f->GetDeclaringClass()->AssertInitializedOrInitializingInThread(self);
-
-  // Report this field access to instrumentation if needed. Since we only have the offset of
-  // the field from the base of the object, we need to look for it first.
-  instrumentation::Instrumentation* instrumentation = Runtime::Current()->GetInstrumentation();
-  if (UNLIKELY(instrumentation->HasFieldWriteListeners())) {
-    StackHandleScope<1> hs(self);
-    // Wrap in handle wrapper in case the listener does thread suspension.
-    HandleWrapperObjPtr<mirror::Object> h(hs.NewHandleWrapper(&obj));
-    ObjPtr<mirror::Object> this_object = f->IsStatic() ? nullptr : obj;
-    instrumentation->FieldWriteEvent(self, this_object.Ptr(),
-                                     shadow_frame.GetMethod(),
-                                     shadow_frame.GetDexPC(),
-                                     f,
-                                     value);
-  }
-
-  switch (field_type) {
-    case Primitive::kPrimBoolean:
-      f->SetBoolean<transaction_active>(obj, value.GetZ());
-      break;
-    case Primitive::kPrimByte:
-      f->SetByte<transaction_active>(obj, value.GetB());
-      break;
-    case Primitive::kPrimChar:
-      f->SetChar<transaction_active>(obj, value.GetC());
-      break;
-    case Primitive::kPrimShort:
-      f->SetShort<transaction_active>(obj, value.GetS());
-      break;
-    case Primitive::kPrimInt:
-      f->SetInt<transaction_active>(obj, value.GetI());
-      break;
-    case Primitive::kPrimLong:
-      f->SetLong<transaction_active>(obj, value.GetJ());
-      break;
-    case Primitive::kPrimNot: {
-      ObjPtr<mirror::Object> reg = value.GetL();
-      if (do_assignability_check && reg != nullptr) {
-        // FieldHelper::GetType can resolve classes, use a handle wrapper which will restore the
-        // object in the destructor.
-        ObjPtr<mirror::Class> field_class;
-        {
-          StackHandleScope<2> hs(self);
-          HandleWrapperObjPtr<mirror::Object> h_reg(hs.NewHandleWrapper(&reg));
-          HandleWrapperObjPtr<mirror::Object> h_obj(hs.NewHandleWrapper(&obj));
-          field_class = f->GetType<true>();
-        }
-        if (!reg->VerifierInstanceOf(field_class.Ptr())) {
-          // This should never happen.
-          std::string temp1, temp2, temp3;
-          self->ThrowNewExceptionF("Ljava/lang/InternalError;",
-                                   "Put '%s' that is not instance of field '%s' in '%s'",
-                                   reg->GetClass()->GetDescriptor(&temp1),
-                                   field_class->GetDescriptor(&temp2),
-                                   f->GetDeclaringClass()->GetDescriptor(&temp3));
-          return false;
-        }
-      }
-      f->SetObj<transaction_active>(obj, reg);
-      break;
-    }
-    default:
-      LOG(FATAL) << "Unreachable: " << field_type;
-      UNREACHABLE();
-  }
-  return true;
-}
-
 template<FindFieldType find_type, Primitive::Type field_type, bool do_access_check,
          bool transaction_active>
 bool DoFieldPut(Thread* self, const ShadowFrame& shadow_frame, const Instruction* inst,
@@ -511,46 +302,6 @@
 #undef EXPLICIT_DO_FIELD_PUT_ALL_TEMPLATE_DECL
 #undef EXPLICIT_DO_FIELD_PUT_TEMPLATE_DECL
 
-// Helper for setters in invoke-polymorphic.
-bool DoFieldPutForInvokePolymorphic(Thread* self,
-                                    ShadowFrame& shadow_frame,
-                                    ObjPtr<mirror::Object>& obj,
-                                    ArtField* field,
-                                    Primitive::Type field_type,
-                                    const JValue& value)
-    REQUIRES_SHARED(Locks::mutator_lock_) {
-  static const bool kDoCheckAssignability = false;
-  static const bool kTransaction = false;
-  switch (field_type) {
-    case Primitive::kPrimBoolean:
-      return DoFieldPutCommon<Primitive::kPrimBoolean, kDoCheckAssignability, kTransaction>(
-          self, shadow_frame, obj, field, value);
-    case Primitive::kPrimByte:
-      return DoFieldPutCommon<Primitive::kPrimByte, kDoCheckAssignability, kTransaction>(
-          self, shadow_frame, obj, field, value);
-    case Primitive::kPrimChar:
-      return DoFieldPutCommon<Primitive::kPrimChar, kDoCheckAssignability, kTransaction>(
-          self, shadow_frame, obj, field, value);
-    case Primitive::kPrimShort:
-      return DoFieldPutCommon<Primitive::kPrimShort, kDoCheckAssignability, kTransaction>(
-          self, shadow_frame, obj, field, value);
-    case Primitive::kPrimInt:
-    case Primitive::kPrimFloat:
-      return DoFieldPutCommon<Primitive::kPrimInt, kDoCheckAssignability, kTransaction>(
-          self, shadow_frame, obj, field, value);
-    case Primitive::kPrimLong:
-    case Primitive::kPrimDouble:
-      return DoFieldPutCommon<Primitive::kPrimLong, kDoCheckAssignability, kTransaction>(
-          self, shadow_frame, obj, field, value);
-    case Primitive::kPrimNot:
-      return DoFieldPutCommon<Primitive::kPrimNot, kDoCheckAssignability, kTransaction>(
-          self, shadow_frame, obj, field, value);
-    case Primitive::kPrimVoid:
-      LOG(FATAL) << "Unreachable: " << field_type;
-      UNREACHABLE();
-  }
-}
-
 template<Primitive::Type field_type, bool transaction_active>
 bool DoIPutQuick(const ShadowFrame& shadow_frame, const Instruction* inst, uint16_t inst_data) {
   ObjPtr<mirror::Object> obj = shadow_frame.GetVRegReference(inst->VRegB_22c(inst_data));
@@ -697,36 +448,6 @@
                                        uint32_t vregC) REQUIRES_SHARED(Locks::mutator_lock_);
 
 template <bool is_range>
-static ALWAYS_INLINE bool DoCallPolymorphic(ArtMethod* called_method,
-                                            Handle<mirror::MethodType> callsite_type,
-                                            Handle<mirror::MethodType> target_type,
-                                            Thread* self,
-                                            ShadowFrame& shadow_frame,
-                                            JValue* result,
-                                            uint32_t (&arg)[Instruction::kMaxVarArgRegs],
-                                            uint32_t vregC,
-                                            const MethodHandleKind handle_kind)
-  REQUIRES_SHARED(Locks::mutator_lock_);
-
-template <bool is_range>
-static ALWAYS_INLINE bool DoCallTransform(ArtMethod* called_method,
-                                          Handle<mirror::MethodType> callsite_type,
-                                          Handle<mirror::MethodType> callee_type,
-                                          Thread* self,
-                                          ShadowFrame& shadow_frame,
-                                          Handle<mirror::MethodHandleImpl> receiver,
-                                          JValue* result,
-                                          uint32_t (&arg)[Instruction::kMaxVarArgRegs],
-                                          uint32_t vregC) REQUIRES_SHARED(Locks::mutator_lock_);
-
-ALWAYS_INLINE void PerformCall(Thread* self,
-                               const DexFile::CodeItem* code_item,
-                               ArtMethod* caller_method,
-                               const size_t first_dest_reg,
-                               ShadowFrame* callee_frame,
-                               JValue* result) REQUIRES_SHARED(Locks::mutator_lock_);
-
-template <bool is_range>
 ALWAYS_INLINE void CopyRegisters(ShadowFrame& caller_frame,
                                  ShadowFrame* callee_frame,
                                  const uint32_t (&arg)[Instruction::kMaxVarArgRegs],
@@ -798,55 +519,12 @@
   }
 }
 
-inline static bool IsInvokeExact(const DexFile& dex_file, int invoke_method_idx) {
-  // This check uses string comparison as it needs less code and data
-  // to do than fetching the associated ArtMethod from the DexCache
-  // and checking against ArtMethods in the well known classes. The
-  // verifier needs to perform a more rigorous check.
-  const char* method_name = dex_file.GetMethodName(dex_file.GetMethodId(invoke_method_idx));
-  bool is_invoke_exact = (0 == strcmp(method_name, "invokeExact"));
-  DCHECK(is_invoke_exact || (0 == strcmp(method_name, "invoke")));
-  return is_invoke_exact;
-}
-
-inline static ObjPtr<mirror::Class> GetAndInitializeDeclaringClass(Thread* self, ArtField* field)
-    REQUIRES_SHARED(Locks::mutator_lock_) {
-  // Method handle invocations on static fields should ensure class is
-  // initialized. This usually happens when an instance is constructed
-  // or class members referenced, but this is not guaranteed when
-  // looking up method handles.
-  ObjPtr<mirror::Class> klass = field->GetDeclaringClass();
-  if (UNLIKELY(!klass->IsInitialized())) {
-    StackHandleScope<1> hs(self);
-    HandleWrapperObjPtr<mirror::Class> h(hs.NewHandleWrapper(&klass));
-    if (!Runtime::Current()->GetClassLinker()->EnsureInitialized(self, h, true, true)) {
-      DCHECK(self->IsExceptionPending());
-      return nullptr;
-    }
-  }
-  return klass;
-}
-
-// Returns true iff. the callsite type for a polymorphic invoke is transformer
-// like, i.e that it has a single input argument whose type is
-// dalvik.system.EmulatedStackFrame.
-static inline bool IsCallerTransformer(Handle<mirror::MethodType> callsite_type)
-    REQUIRES_SHARED(Locks::mutator_lock_) {
-  ObjPtr<mirror::ObjectArray<mirror::Class>> param_types(callsite_type->GetPTypes());
-  if (param_types->GetLength() == 1) {
-    ObjPtr<mirror::Class> param(param_types->GetWithoutChecks(0));
-    return param == WellKnownClasses::ToClass(WellKnownClasses::dalvik_system_EmulatedStackFrame);
-  }
-
-  return false;
-}
-
 template<bool is_range, bool do_access_check>
-inline bool DoInvokePolymorphic(Thread* self,
-                                ShadowFrame& shadow_frame,
-                                const Instruction* inst,
-                                uint16_t inst_data,
-                                JValue* result)
+bool DoInvokePolymorphic(Thread* self,
+                         ShadowFrame& shadow_frame,
+                         const Instruction* inst,
+                         uint16_t inst_data,
+                         JValue* result)
     REQUIRES_SHARED(Locks::mutator_lock_) {
   // Invoke-polymorphic instructions always take a receiver. i.e, they are never static.
   const uint32_t vRegC = (is_range) ? inst->VRegC_4rcc() : inst->VRegC_45cc();
@@ -857,15 +535,10 @@
   // and provides sane return result in error cases.
   result->SetJ(0);
 
-  // Determine if this invocation is MethodHandle.invoke() or
-  // MethodHandle.invokeExact().
-  bool is_invoke_exact = IsInvokeExact(shadow_frame.GetMethod()->GetDeclaringClass()->GetDexFile(),
-                                       invoke_method_idx);
-
   // The invoke_method_idx here is the name of the signature polymorphic method that
   // was symbolically invoked in bytecode (say MethodHandle.invoke or MethodHandle.invokeExact)
   // and not the method that we'll dispatch to in the end.
-  StackHandleScope<6> hs(self);
+  StackHandleScope<5> hs(self);
   Handle<mirror::MethodHandleImpl> method_handle(hs.NewHandle(
       ObjPtr<mirror::MethodHandleImpl>::DownCast(
           MakeObjPtr(shadow_frame.GetVRegReference(vRegC)))));
@@ -877,7 +550,7 @@
   }
 
   // The vRegH value gives the index of the proto_id associated with this
-  // signature polymorphic callsite.
+  // signature polymorphic call site.
   const uint32_t callsite_proto_id = (is_range) ? inst->VRegH_4rcc() : inst->VRegH_45cc();
 
   // Call through to the classlinker and ask it to resolve the static type associated
@@ -896,224 +569,43 @@
     return false;
   }
 
-  const MethodHandleKind handle_kind = method_handle->GetHandleKind();
-  Handle<mirror::MethodType> handle_type(hs.NewHandle(method_handle->GetMethodType()));
-  CHECK(handle_type.Get() != nullptr);
-  {
-    // We need to check the nominal type of the handle in addition to the
-    // real type. The "nominal" type is present when MethodHandle.asType is
-    // called any handle, and results in the declared type of the handle
-    // changing.
-    ObjPtr<mirror::MethodType> nominal_type(method_handle->GetNominalType());
-    ObjPtr<mirror::MethodType> check_type(nullptr);
-    if (LIKELY(nominal_type.Ptr() == nullptr)) {
-      check_type.Assign(handle_type.Get());
-    } else {
-      check_type.Assign(nominal_type.Ptr());
-    }
+  ArtMethod* invoke_method =
+      class_linker->ResolveMethod<ClassLinker::kForceICCECheck>(self,
+                                                                invoke_method_idx,
+                                                                shadow_frame.GetMethod(),
+                                                                kVirtual);
 
-    if (is_invoke_exact) {
-      if (UNLIKELY(!callsite_type->IsExactMatch(check_type.Ptr()))) {
-        ThrowWrongMethodTypeException(check_type.Ptr(), callsite_type.Get());
-        return false;
-      }
-    } else if (!IsInvokeTransform(handle_kind)) {
-      if (UNLIKELY(!IsCallerTransformer(callsite_type) &&
-                   !callsite_type->IsConvertible(check_type.Ptr()))) {
-        ThrowWrongMethodTypeException(check_type.Ptr(), callsite_type.Get());
-        return false;
-      }
-    }
-  }
-
-  uint32_t arg[Instruction::kMaxVarArgRegs] = {};
-  uint32_t first_src_reg = 0;
+  // There is a common dispatch method for method handles that takes
+  // arguments either from a range or an array of arguments depending
+  // on whether the DEX instruction is invoke-polymorphic/range or
+  // invoke-polymorphic. The array here is for the latter.
+  uint32_t args[Instruction::kMaxVarArgRegs] = {};
   if (is_range) {
-    first_src_reg = (inst->VRegC_4rcc() + 1);
+    // VRegC is the register holding the method handle. Arguments passed
+    // to the method handle's target do not include the method handle.
+    uint32_t first_arg = inst->VRegC_4rcc() + 1;
+    return DoInvokePolymorphic<is_range, do_access_check>(self,
+                                                          invoke_method,
+                                                          shadow_frame,
+                                                          method_handle,
+                                                          callsite_type,
+                                                          args /* unused */,
+                                                          first_arg,
+                                                          result);
   } else {
-    inst->GetVarArgs(arg, inst_data);
-    arg[0] = arg[1];
-    arg[1] = arg[2];
-    arg[2] = arg[3];
-    arg[3] = arg[4];
-    arg[4] = 0;
-    first_src_reg = arg[0];
-  }
-
-  if (IsInvoke(handle_kind)) {
-    // Get the method we're actually invoking along with the kind of
-    // invoke that is desired. We don't need to perform access checks at this
-    // point because they would have been performed on our behalf at the point
-    // of creation of the method handle.
-    ArtMethod* called_method = method_handle->GetTargetMethod();
-    CHECK(called_method != nullptr);
-
-    if (handle_kind == kInvokeVirtual || handle_kind == kInvokeInterface) {
-      // TODO: Unfortunately, we have to postpone dynamic receiver based checks
-      // because the receiver might be cast or might come from an emulated stack
-      // frame, which means that it is unknown at this point. We perform these
-      // checks inside DoCallPolymorphic right before we do the actual invoke.
-    } else if (handle_kind == kInvokeDirect) {
-      // String constructors are a special case, they are replaced with StringFactory
-      // methods.
-      if (called_method->IsConstructor() && called_method->GetDeclaringClass()->IsStringClass()) {
-        DCHECK(handle_type->GetRType()->IsStringClass());
-        called_method = WellKnownClasses::StringInitToStringFactory(called_method);
-      }
-    } else if (handle_kind == kInvokeSuper) {
-      ObjPtr<mirror::Class> declaring_class = called_method->GetDeclaringClass();
-
-      // Note that we're not dynamically dispatching on the type of the receiver
-      // here. We use the static type of the "receiver" object that we've
-      // recorded in the method handle's type, which will be the same as the
-      // special caller that was specified at the point of lookup.
-      ObjPtr<mirror::Class> referrer_class = handle_type->GetPTypes()->Get(0);
-      if (!declaring_class->IsInterface()) {
-        ObjPtr<mirror::Class> super_class = referrer_class->GetSuperClass();
-        uint16_t vtable_index = called_method->GetMethodIndex();
-        DCHECK(super_class != nullptr);
-        DCHECK(super_class->HasVTable());
-        // Note that super_class is a super of referrer_class and called_method
-        // will always be declared by super_class (or one of its super classes).
-        DCHECK_LT(vtable_index, super_class->GetVTableLength());
-        called_method = super_class->GetVTableEntry(vtable_index, kRuntimePointerSize);
-      } else {
-        called_method = referrer_class->FindVirtualMethodForInterfaceSuper(
-            called_method, kRuntimePointerSize);
-      }
-
-      CHECK(called_method != nullptr);
-    }
-
-    if (IsInvokeTransform(handle_kind)) {
-      // There are two cases here - method handles representing regular
-      // transforms and those representing call site transforms. Method
-      // handles for call site transforms adapt their MethodType to match
-      // the call site. For these, the |callee_type| is the same as the
-      // |callsite_type|. The VarargsCollector is such a tranform, its
-      // method type depends on the call site, ie. x(a) or x(a, b), or
-      // x(a, b, c). The VarargsCollector invokes a variable arity method
-      // with the arity arguments in an array.
-      Handle<mirror::MethodType> callee_type =
-          (handle_kind == kInvokeCallSiteTransform) ? callsite_type : handle_type;
-      return DoCallTransform<is_range>(called_method,
-                                       callsite_type,
-                                       callee_type,
-                                       self,
-                                       shadow_frame,
-                                       method_handle /* receiver */,
-                                       result,
-                                       arg,
-                                       first_src_reg);
-    } else {
-      return DoCallPolymorphic<is_range>(called_method,
-                                         callsite_type,
-                                         handle_type,
-                                         self,
-                                         shadow_frame,
-                                         result,
-                                         arg,
-                                         first_src_reg,
-                                         handle_kind);
-    }
-  } else {
-    DCHECK(!is_range);
-    ArtField* field = method_handle->GetTargetField();
-    Primitive::Type field_type = field->GetTypeAsPrimitiveType();
-
-    switch (handle_kind) {
-      case kInstanceGet: {
-        ObjPtr<mirror::Object> obj = shadow_frame.GetVRegReference(first_src_reg);
-        DoFieldGetForInvokePolymorphic(self, shadow_frame, obj, field, field_type, result);
-        if (!ConvertReturnValue(callsite_type, handle_type, result)) {
-          DCHECK(self->IsExceptionPending());
-          return false;
-        }
-        return true;
-      }
-      case kStaticGet: {
-        ObjPtr<mirror::Object> obj = GetAndInitializeDeclaringClass(self, field);
-        if (obj == nullptr) {
-          DCHECK(self->IsExceptionPending());
-          return false;
-        }
-        DoFieldGetForInvokePolymorphic(self, shadow_frame, obj, field, field_type, result);
-        if (!ConvertReturnValue(callsite_type, handle_type, result)) {
-          DCHECK(self->IsExceptionPending());
-          return false;
-        }
-        return true;
-      }
-      case kInstancePut: {
-        JValue value = GetFieldValue(shadow_frame, field_type, arg[1]);
-        if (!ConvertArgumentValue(callsite_type, handle_type, 1, &value)) {
-          DCHECK(self->IsExceptionPending());
-          return false;
-        }
-        ObjPtr<mirror::Object> obj = shadow_frame.GetVRegReference(first_src_reg);
-        return DoFieldPutForInvokePolymorphic(self, shadow_frame, obj, field, field_type, value);
-      }
-      case kStaticPut: {
-        JValue value = GetFieldValue(shadow_frame, field_type, arg[0]);
-        if (!ConvertArgumentValue(callsite_type, handle_type, 0, &value)) {
-          DCHECK(self->IsExceptionPending());
-          return false;
-        }
-        ObjPtr<mirror::Object> obj = field->GetDeclaringClass();
-        return DoFieldPutForInvokePolymorphic(self, shadow_frame, obj, field, field_type, value);
-      }
-      default:
-        LOG(FATAL) << "Unreachable: " << handle_kind;
-        UNREACHABLE();
-    }
-  }
-}
-
-// Calculate the number of ins for a proxy or native method, where we
-// can't just look at the code item.
-static inline size_t GetInsForProxyOrNativeMethod(ArtMethod* method)
-    REQUIRES_SHARED(Locks::mutator_lock_) {
-  DCHECK(method->IsNative() || method->IsProxyMethod());
-
-  method = method->GetInterfaceMethodIfProxy(kRuntimePointerSize);
-  size_t num_ins = 0;
-  // Separate accounting for the receiver, which isn't a part of the
-  // shorty.
-  if (!method->IsStatic()) {
-    ++num_ins;
-  }
-
-  uint32_t shorty_len = 0;
-  const char* shorty = method->GetShorty(&shorty_len);
-  for (size_t i = 1; i < shorty_len; ++i) {
-    const char c = shorty[i];
-    ++num_ins;
-    if (c == 'J' || c == 'D') {
-      ++num_ins;
-    }
-  }
-
-  return num_ins;
-}
-
-inline void PerformCall(Thread* self,
-                        const DexFile::CodeItem* code_item,
-                        ArtMethod* caller_method,
-                        const size_t first_dest_reg,
-                        ShadowFrame* callee_frame,
-                        JValue* result) {
-  if (LIKELY(Runtime::Current()->IsStarted())) {
-    ArtMethod* target = callee_frame->GetMethod();
-    if (ClassLinker::ShouldUseInterpreterEntrypoint(
-        target,
-        target->GetEntryPointFromQuickCompiledCode())) {
-      ArtInterpreterToInterpreterBridge(self, code_item, callee_frame, result);
-    } else {
-      ArtInterpreterToCompiledCodeBridge(
-          self, caller_method, code_item, callee_frame, result);
-    }
-  } else {
-    UnstartedRuntime::Invoke(self, code_item, callee_frame, result, first_dest_reg);
+    // Get the register arguments for the invoke.
+    inst->GetVarArgs(args, inst_data);
+    // Drop the first register which is the method handle performing the invoke.
+    memcpy(args, args + 1, sizeof(args[0]) * (Instruction::kMaxVarArgRegs - 1));
+    args[Instruction::kMaxVarArgRegs - 1] = 0;
+    return DoInvokePolymorphic<is_range, do_access_check>(self,
+                                                          invoke_method,
+                                                          shadow_frame,
+                                                          method_handle,
+                                                          callsite_type,
+                                                          args,
+                                                          args[0],
+                                                          result);
   }
 }
 
@@ -1139,217 +631,6 @@
   }
 }
 
-template <bool is_range>
-static inline bool DoCallPolymorphic(ArtMethod* called_method,
-                                     Handle<mirror::MethodType> callsite_type,
-                                     Handle<mirror::MethodType> target_type,
-                                     Thread* self,
-                                     ShadowFrame& shadow_frame,
-                                     JValue* result,
-                                     uint32_t (&arg)[Instruction::kMaxVarArgRegs],
-                                     uint32_t first_src_reg,
-                                     const MethodHandleKind handle_kind) {
-  // Compute method information.
-  const DexFile::CodeItem* code_item = called_method->GetCodeItem();
-
-  // Number of registers for the callee's call frame. Note that for non-exact
-  // invokes, we always derive this information from the callee method. We
-  // cannot guarantee during verification that the number of registers encoded
-  // in the invoke is equal to the number of ins for the callee. This is because
-  // some transformations (such as boxing a long -> Long or wideining an
-  // int -> long will change that number.
-  uint16_t num_regs;
-  size_t num_input_regs;
-  size_t first_dest_reg;
-  if (LIKELY(code_item != nullptr)) {
-    num_regs = code_item->registers_size_;
-    first_dest_reg = num_regs - code_item->ins_size_;
-    num_input_regs = code_item->ins_size_;
-    // Parameter registers go at the end of the shadow frame.
-    DCHECK_NE(first_dest_reg, (size_t)-1);
-  } else {
-    // No local regs for proxy and native methods.
-    DCHECK(called_method->IsNative() || called_method->IsProxyMethod());
-    num_regs = num_input_regs = GetInsForProxyOrNativeMethod(called_method);
-    first_dest_reg = 0;
-  }
-
-  // Allocate shadow frame on the stack.
-  ShadowFrameAllocaUniquePtr shadow_frame_unique_ptr =
-      CREATE_SHADOW_FRAME(num_regs, &shadow_frame, called_method, /* dex pc */ 0);
-  ShadowFrame* new_shadow_frame = shadow_frame_unique_ptr.get();
-
-  // Whether this polymorphic invoke was issued by a transformer method.
-  bool is_caller_transformer = false;
-  // Thread might be suspended during PerformArgumentConversions due to the
-  // allocations performed during boxing.
-  {
-    ScopedStackedShadowFramePusher pusher(
-        self, new_shadow_frame, StackedShadowFrameType::kShadowFrameUnderConstruction);
-    if (callsite_type->IsExactMatch(target_type.Get())) {
-      // This is an exact invoke, we can take the fast path of just copying all
-      // registers without performing any argument conversions.
-      CopyRegisters<is_range>(shadow_frame,
-                              new_shadow_frame,
-                              arg,
-                              first_src_reg,
-                              first_dest_reg,
-                              num_input_regs);
-    } else {
-      // This includes the case where we're entering this invoke-polymorphic
-      // from a transformer method. In that case, the callsite_type will contain
-      // a single argument of type dalvik.system.EmulatedStackFrame. In that
-      // case, we'll have to unmarshal the EmulatedStackFrame into the
-      // new_shadow_frame and perform argument conversions on it.
-      if (IsCallerTransformer(callsite_type)) {
-        is_caller_transformer = true;
-        // The emulated stack frame is the first and only argument when we're coming
-        // through from a transformer.
-        ObjPtr<mirror::EmulatedStackFrame> emulated_stack_frame(
-            reinterpret_cast<mirror::EmulatedStackFrame*>(
-                shadow_frame.GetVRegReference(first_src_reg)));
-        if (!emulated_stack_frame->WriteToShadowFrame(self,
-                                                      target_type,
-                                                      first_dest_reg,
-                                                      new_shadow_frame)) {
-          DCHECK(self->IsExceptionPending());
-          result->SetL(0);
-          return false;
-        }
-      } else if (!ConvertAndCopyArgumentsFromCallerFrame<is_range>(self,
-                                                                   callsite_type,
-                                                                   target_type,
-                                                                   shadow_frame,
-                                                                   first_src_reg,
-                                                                   first_dest_reg,
-                                                                   arg,
-                                                                   new_shadow_frame)) {
-        DCHECK(self->IsExceptionPending());
-        result->SetL(0);
-        return false;
-      }
-    }
-  }
-
-  // See TODO in DoInvokePolymorphic : We need to perform this dynamic, receiver
-  // based dispatch right before we perform the actual call, because the
-  // receiver isn't known very early.
-  if (handle_kind == kInvokeVirtual || handle_kind == kInvokeInterface) {
-    ObjPtr<mirror::Object> receiver(new_shadow_frame->GetVRegReference(first_dest_reg));
-    ObjPtr<mirror::Class> declaring_class(called_method->GetDeclaringClass());
-    // Verify that _vRegC is an object reference and of the type expected by
-    // the receiver.
-    if (!VerifyObjectIsClass(receiver, declaring_class)) {
-      DCHECK(self->IsExceptionPending());
-      return false;
-    }
-
-    called_method = receiver->GetClass()->FindVirtualMethodForVirtualOrInterface(
-        called_method, kRuntimePointerSize);
-  }
-
-  PerformCall(self, code_item, shadow_frame.GetMethod(), first_dest_reg, new_shadow_frame, result);
-  if (self->IsExceptionPending()) {
-    return false;
-  }
-
-  // If the caller of this signature polymorphic method was a transformer,
-  // we need to copy the result back out to the emulated stack frame.
-  if (is_caller_transformer) {
-    StackHandleScope<2> hs(self);
-    Handle<mirror::EmulatedStackFrame> emulated_stack_frame(
-        hs.NewHandle(reinterpret_cast<mirror::EmulatedStackFrame*>(
-            shadow_frame.GetVRegReference(first_src_reg))));
-    Handle<mirror::MethodType> emulated_stack_type(hs.NewHandle(emulated_stack_frame->GetType()));
-    JValue local_result;
-    local_result.SetJ(result->GetJ());
-
-    if (ConvertReturnValue(emulated_stack_type, target_type, &local_result)) {
-      emulated_stack_frame->SetReturnValue(self, local_result);
-      return true;
-    } else {
-      DCHECK(self->IsExceptionPending());
-      return false;
-    }
-  } else {
-    return ConvertReturnValue(callsite_type, target_type, result);
-  }
-}
-
-template <bool is_range>
-static inline bool DoCallTransform(ArtMethod* called_method,
-                                   Handle<mirror::MethodType> callsite_type,
-                                   Handle<mirror::MethodType> callee_type,
-                                   Thread* self,
-                                   ShadowFrame& shadow_frame,
-                                   Handle<mirror::MethodHandleImpl> receiver,
-                                   JValue* result,
-                                   uint32_t (&arg)[Instruction::kMaxVarArgRegs],
-                                   uint32_t first_src_reg) {
-  // This can be fixed to two, because the method we're calling here
-  // (MethodHandle.transformInternal) doesn't have any locals and the signature
-  // is known :
-  //
-  // private MethodHandle.transformInternal(EmulatedStackFrame sf);
-  //
-  // This means we need only two vregs :
-  // - One for the receiver object.
-  // - One for the only method argument (an EmulatedStackFrame).
-  static constexpr size_t kNumRegsForTransform = 2;
-
-  const DexFile::CodeItem* code_item = called_method->GetCodeItem();
-  DCHECK(code_item != nullptr);
-  DCHECK_EQ(kNumRegsForTransform, code_item->registers_size_);
-  DCHECK_EQ(kNumRegsForTransform, code_item->ins_size_);
-
-  ShadowFrameAllocaUniquePtr shadow_frame_unique_ptr =
-      CREATE_SHADOW_FRAME(kNumRegsForTransform, &shadow_frame, called_method, /* dex pc */ 0);
-  ShadowFrame* new_shadow_frame = shadow_frame_unique_ptr.get();
-
-  StackHandleScope<1> hs(self);
-  MutableHandle<mirror::EmulatedStackFrame> sf(hs.NewHandle<mirror::EmulatedStackFrame>(nullptr));
-  if (IsCallerTransformer(callsite_type)) {
-    // If we're entering this transformer from another transformer, we can pass
-    // through the handle directly to the callee, instead of having to
-    // instantiate a new stack frame based on the shadow frame.
-    sf.Assign(reinterpret_cast<mirror::EmulatedStackFrame*>(
-        shadow_frame.GetVRegReference(first_src_reg)));
-  } else {
-    sf.Assign(mirror::EmulatedStackFrame::CreateFromShadowFrameAndArgs<is_range>(
-        self,
-        callsite_type,
-        callee_type,
-        shadow_frame,
-        first_src_reg,
-        arg));
-
-    // Something went wrong while creating the emulated stack frame, we should
-    // throw the pending exception.
-    if (sf.Get() == nullptr) {
-      DCHECK(self->IsExceptionPending());
-      return false;
-    }
-  }
-
-  new_shadow_frame->SetVRegReference(0, receiver.Get());
-  new_shadow_frame->SetVRegReference(1, sf.Get());
-
-  PerformCall(self,
-              code_item,
-              shadow_frame.GetMethod(),
-              0 /* first dest reg */,
-              new_shadow_frame,
-              result);
-  if (self->IsExceptionPending()) {
-    return false;
-  }
-
-  // If the called transformer method we called has returned a value, then we
-  // need to copy it back to |result|.
-  sf->GetReturnValue(self, result);
-  return ConvertReturnValue(callsite_type, callee_type, result);
-}
-
 template <bool is_range,
           bool do_assignability_check>
 static inline bool DoCallCommon(ArtMethod* called_method,
diff --git a/runtime/interpreter/interpreter_common.h b/runtime/interpreter/interpreter_common.h
index 423f054..aeb438f 100644
--- a/runtime/interpreter/interpreter_common.h
+++ b/runtime/interpreter/interpreter_common.h
@@ -33,6 +33,7 @@
 #include "base/logging.h"
 #include "base/macros.h"
 #include "class_linker-inl.h"
+#include "common_dex_operations.h"
 #include "common_throws.h"
 #include "dex_file-inl.h"
 #include "dex_instruction-inl.h"
@@ -48,6 +49,7 @@
 #include "obj_ptr.h"
 #include "stack.h"
 #include "thread.h"
+#include "unstarted_runtime.h"
 #include "well_known_classes.h"
 
 namespace art {
@@ -153,8 +155,10 @@
 
 // Performs a signature polymorphic invoke (invoke-polymorphic/invoke-polymorphic-range).
 template<bool is_range, bool do_access_check>
-bool DoInvokePolymorphic(Thread* self, ShadowFrame& shadow_frame,
-                         const Instruction* inst, uint16_t inst_data,
+bool DoInvokePolymorphic(Thread* self,
+                         ShadowFrame& shadow_frame,
+                         const Instruction* inst,
+                         uint16_t inst_data,
                          JValue* result);
 
 // Handles invoke-virtual-quick and invoke-virtual-quick-range instructions.
@@ -251,17 +255,16 @@
     }
   }
   ArtMethod* method = shadow_frame.GetMethod();
-  ObjPtr<mirror::Class> declaring_class = method->GetDeclaringClass();
   // MethodVerifier refuses methods with string_idx out of bounds.
   DCHECK_LT(string_idx.index_ % mirror::DexCache::kDexCacheStringCacheSize,
-            declaring_class->GetDexFile().NumStringIds());
+            method->GetDexFile()->NumStringIds());
   ObjPtr<mirror::String> string_ptr =
-      mirror::StringDexCachePair::Lookup(declaring_class->GetDexCache()->GetStrings(),
+      mirror::StringDexCachePair::Lookup(method->GetDexCache()->GetStrings(),
                                          string_idx.index_,
                                          mirror::DexCache::kDexCacheStringCacheSize).Read();
   if (UNLIKELY(string_ptr == nullptr)) {
     StackHandleScope<1> hs(self);
-    Handle<mirror::DexCache> dex_cache(hs.NewHandle(declaring_class->GetDexCache()));
+    Handle<mirror::DexCache> dex_cache(hs.NewHandle(method->GetDexCache()));
     string_ptr = Runtime::Current()->GetClassLinker()->ResolveString(*method->GetDexFile(),
                                                                      string_idx,
                                                                      dex_cache);
diff --git a/runtime/jdwp/jdwp_adb.cc b/runtime/jdwp/jdwp_adb.cc
index 0eff2ab..d8869ad 100644
--- a/runtime/jdwp/jdwp_adb.cc
+++ b/runtime/jdwp/jdwp_adb.cc
@@ -24,6 +24,7 @@
 
 #include "base/logging.h"
 #include "jdwp/jdwp_priv.h"
+#include "thread-inl.h"
 
 #ifdef ART_TARGET_ANDROID
 #include "cutils/sockets.h"
@@ -57,7 +58,9 @@
 
 struct JdwpAdbState : public JdwpNetStateBase {
  public:
-  explicit JdwpAdbState(JdwpState* state) : JdwpNetStateBase(state) {
+  explicit JdwpAdbState(JdwpState* state)
+      : JdwpNetStateBase(state),
+        state_lock_("JdwpAdbState lock", kJdwpAdbStateLock) {
     control_sock_ = -1;
     shutting_down_ = false;
 
@@ -77,20 +80,23 @@
     }
   }
 
-  virtual bool Accept();
+  virtual bool Accept() REQUIRES(!state_lock_);
 
   virtual bool Establish(const JdwpOptions*) {
     return false;
   }
 
-  virtual void Shutdown() {
-    shutting_down_ = true;
-
-    int control_sock = this->control_sock_;
-    int local_clientSock = this->clientSock;
-
-    /* clear these out so it doesn't wake up and try to reuse them */
-    this->control_sock_ = this->clientSock = -1;
+  virtual void Shutdown() REQUIRES(!state_lock_) {
+    int control_sock;
+    int local_clientSock;
+    {
+      MutexLock mu(Thread::Current(), state_lock_);
+      shutting_down_ = true;
+      control_sock = this->control_sock_;
+      local_clientSock = this->clientSock;
+      /* clear these out so it doesn't wake up and try to reuse them */
+      this->control_sock_ = this->clientSock = -1;
+    }
 
     if (local_clientSock != -1) {
       shutdown(local_clientSock, SHUT_RDWR);
@@ -103,13 +109,27 @@
     WakePipe();
   }
 
-  virtual bool ProcessIncoming();
+  virtual bool ProcessIncoming() REQUIRES(!state_lock_);
 
  private:
-  int ReceiveClientFd();
+  int ReceiveClientFd() REQUIRES(!state_lock_);
 
-  int control_sock_;
-  bool shutting_down_;
+  bool IsDown() REQUIRES(!state_lock_) {
+    MutexLock mu(Thread::Current(), state_lock_);
+    return shutting_down_;
+  }
+
+  int ControlSock() REQUIRES(!state_lock_) {
+    MutexLock mu(Thread::Current(), state_lock_);
+    if (shutting_down_) {
+      CHECK_EQ(control_sock_, -1);
+    }
+    return control_sock_;
+  }
+
+  int control_sock_ GUARDED_BY(state_lock_);
+  bool shutting_down_ GUARDED_BY(state_lock_);
+  Mutex state_lock_;
 
   socklen_t control_addr_len_;
   union {
@@ -162,12 +182,13 @@
   cmsg->cmsg_type  = SCM_RIGHTS;
   (reinterpret_cast<int*>(CMSG_DATA(cmsg)))[0] = -1;
 
-  int rc = TEMP_FAILURE_RETRY(recvmsg(control_sock_, &msg, 0));
+  int rc = TEMP_FAILURE_RETRY(recvmsg(ControlSock(), &msg, 0));
 
   if (rc <= 0) {
     if (rc == -1) {
-      PLOG(WARNING) << "Receiving file descriptor from ADB failed (socket " << control_sock_ << ")";
+      PLOG(WARNING) << "Receiving file descriptor from ADB failed (socket " << ControlSock() << ")";
     }
+    MutexLock mu(Thread::Current(), state_lock_);
     close(control_sock_);
     control_sock_ = -1;
     return -1;
@@ -189,23 +210,29 @@
   /* first, ensure that we get a connection to the ADB daemon */
 
  retry:
-  if (shutting_down_) {
+  if (IsDown()) {
     return false;
   }
 
-  if (control_sock_ == -1) {
+  if (ControlSock() == -1) {
     int        sleep_ms     = 500;
     const int  sleep_max_ms = 2*1000;
     char       buff[5];
 
-    control_sock_ = socket(PF_UNIX, SOCK_STREAM, 0);
-    if (control_sock_ < 0) {
+    int sock = socket(PF_UNIX, SOCK_STREAM, 0);
+    if (sock < 0) {
       PLOG(ERROR) << "Could not create ADB control socket";
       return false;
     }
-
-    if (!MakePipe()) {
-      return false;
+    {
+      MutexLock mu(Thread::Current(), state_lock_);
+      control_sock_ = sock;
+      if (shutting_down_) {
+        return false;
+      }
+      if (!MakePipe()) {
+        return false;
+      }
     }
 
     snprintf(buff, sizeof(buff), "%04x", getpid());
@@ -225,11 +252,12 @@
        * up after a few minutes in case somebody ships an app with
        * the debuggable flag set.
        */
-      int  ret = connect(control_sock_, &control_addr_.controlAddrPlain, control_addr_len_);
+      int  ret = connect(ControlSock(), &control_addr_.controlAddrPlain, control_addr_len_);
       if (!ret) {
+        int control_sock = ControlSock();
 #ifdef ART_TARGET_ANDROID
-        if (!socket_peer_is_trusted(control_sock_)) {
-          if (shutdown(control_sock_, SHUT_RDWR)) {
+        if (control_sock < 0 || !socket_peer_is_trusted(control_sock)) {
+          if (control_sock >= 0 && shutdown(control_sock, SHUT_RDWR)) {
             PLOG(ERROR) << "trouble shutting down socket";
           }
           return false;
@@ -237,7 +265,7 @@
 #endif
 
         /* now try to send our pid to the ADB daemon */
-        ret = TEMP_FAILURE_RETRY(send(control_sock_, buff, 4, 0));
+        ret = TEMP_FAILURE_RETRY(send(control_sock, buff, 4, 0));
         if (ret >= 0) {
           VLOG(jdwp) << StringPrintf("PID sent as '%.*s' to ADB", 4, buff);
           break;
@@ -256,7 +284,7 @@
       if (sleep_ms > sleep_max_ms) {
         sleep_ms = sleep_max_ms;
       }
-      if (shutting_down_) {
+      if (IsDown()) {
         return false;
       }
     }
@@ -264,9 +292,13 @@
 
   VLOG(jdwp) << "trying to receive file descriptor from ADB";
   /* now we can receive a client file descriptor */
-  clientSock = ReceiveClientFd();
-  if (shutting_down_) {
-    return false;       // suppress logs and additional activity
+  int sock = ReceiveClientFd();
+  {
+    MutexLock mu(Thread::Current(), state_lock_);
+    clientSock = sock;
+    if (shutting_down_) {
+      return false;       // suppress logs and additional activity
+    }
   }
   if (clientSock == -1) {
     if (++retryCount > 5) {
@@ -314,7 +346,7 @@
       FD_ZERO(&readfds);
 
       /* configure fds; note these may get zapped by another thread */
-      fd = control_sock_;
+      fd = ControlSock();
       if (fd >= 0) {
         FD_SET(fd, &readfds);
         if (maxfd < fd) {
@@ -368,13 +400,14 @@
         VLOG(jdwp) << "Got wake-up signal, bailing out of select";
         goto fail;
       }
-      if (control_sock_ >= 0 && FD_ISSET(control_sock_, &readfds)) {
+      int control_sock = ControlSock();
+      if (control_sock >= 0 && FD_ISSET(control_sock, &readfds)) {
         int  sock = ReceiveClientFd();
         if (sock >= 0) {
           LOG(INFO) << "Ignoring second debugger -- accepting and dropping";
           close(sock);
         } else {
-          CHECK_EQ(control_sock_, -1);
+          CHECK_EQ(ControlSock(), -1);
           /*
            * Remote side most likely went away, so our next read
            * on clientSock will fail and throw us out of the loop.
diff --git a/runtime/jit/jit_code_cache.cc b/runtime/jit/jit_code_cache.cc
index f43e30d..6336cdd 100644
--- a/runtime/jit/jit_code_cache.cc
+++ b/runtime/jit/jit_code_cache.cc
@@ -594,6 +594,9 @@
       VLOG(jit) << "JIT discarded jitted code due to invalid single-implementation assumptions.";
       return nullptr;
     }
+    DCHECK(cha_single_implementation_list.empty() || !Runtime::Current()->IsDebuggable())
+        << "Should not be using cha on debuggable apps/runs!";
+
     for (ArtMethod* single_impl : cha_single_implementation_list) {
       Runtime::Current()->GetClassHierarchyAnalysis()->AddDependency(
           single_impl, method, method_header);
@@ -645,6 +648,69 @@
   return CodeCacheSizeLocked();
 }
 
+// This notifies the code cache that the given method has been redefined and that it should remove
+// any cached information it has on the method. All threads must be suspended before calling this
+// method. The compiled code for the method (if there is any) must not be in any threads call stack.
+void JitCodeCache::NotifyMethodRedefined(ArtMethod* method) {
+  MutexLock mu(Thread::Current(), lock_);
+  if (method->IsNative()) {
+    return;
+  }
+  ProfilingInfo* info = method->GetProfilingInfo(kRuntimePointerSize);
+  if (info != nullptr) {
+    auto profile = std::find(profiling_infos_.begin(), profiling_infos_.end(), info);
+    DCHECK(profile != profiling_infos_.end());
+    profiling_infos_.erase(profile);
+  }
+  method->SetProfilingInfo(nullptr);
+  ScopedCodeCacheWrite ccw(code_map_.get());
+  for (auto code_iter = method_code_map_.begin();
+       code_iter != method_code_map_.end();
+       ++code_iter) {
+    if (code_iter->second == method) {
+      FreeCode(code_iter->first);
+      method_code_map_.erase(code_iter);
+    }
+  }
+  auto code_map = osr_code_map_.find(method);
+  if (code_map != osr_code_map_.end()) {
+    osr_code_map_.erase(code_map);
+  }
+}
+
+// This invalidates old_method. Once this function returns one can no longer use old_method to
+// execute code unless it is fixed up. This fixup will happen later in the process of installing a
+// class redefinition.
+// TODO We should add some info to ArtMethod to note that 'old_method' has been invalidated and
+// shouldn't be used since it is no longer logically in the jit code cache.
+// TODO We should add DCHECKS that validate that the JIT is paused when this method is entered.
+void JitCodeCache::MoveObsoleteMethod(ArtMethod* old_method, ArtMethod* new_method) {
+  MutexLock mu(Thread::Current(), lock_);
+  // Update ProfilingInfo to the new one and remove it from the old_method.
+  if (old_method->GetProfilingInfo(kRuntimePointerSize) != nullptr) {
+    DCHECK_EQ(old_method->GetProfilingInfo(kRuntimePointerSize)->GetMethod(), old_method);
+    ProfilingInfo* info = old_method->GetProfilingInfo(kRuntimePointerSize);
+    old_method->SetProfilingInfo(nullptr);
+    // Since the JIT should be paused and all threads suspended by the time this is called these
+    // checks should always pass.
+    DCHECK(!info->IsInUseByCompiler());
+    new_method->SetProfilingInfo(info);
+    info->method_ = new_method;
+  }
+  // Update method_code_map_ to point to the new method.
+  for (auto& it : method_code_map_) {
+    if (it.second == old_method) {
+      it.second = new_method;
+    }
+  }
+  // Update osr_code_map_ to point to the new method.
+  auto code_map = osr_code_map_.find(old_method);
+  if (code_map != osr_code_map_.end()) {
+    osr_code_map_.Put(new_method, code_map->second);
+    osr_code_map_.erase(old_method);
+  }
+}
+
 size_t JitCodeCache::CodeCacheSizeLocked() {
   return used_memory_for_code_;
 }
diff --git a/runtime/jit/jit_code_cache.h b/runtime/jit/jit_code_cache.h
index d97742d..b5e3176 100644
--- a/runtime/jit/jit_code_cache.h
+++ b/runtime/jit/jit_code_cache.h
@@ -75,6 +75,10 @@
       REQUIRES_SHARED(Locks::mutator_lock_)
       REQUIRES(!lock_);
 
+  void NotifyMethodRedefined(ArtMethod* method)
+      REQUIRES(Locks::mutator_lock_)
+      REQUIRES(!lock_);
+
   // Notify to the code cache that the compiler wants to use the
   // profiling info of `method` to drive optimizations,
   // and therefore ensure the returned profiling info object is not
@@ -219,6 +223,11 @@
   void DisallowInlineCacheAccess() REQUIRES(!lock_);
   void BroadcastForInlineCacheAccess() REQUIRES(!lock_);
 
+  // Notify the code cache that the method at the pointer 'old_method' is being moved to the pointer
+  // 'new_method' since it is being made obsolete.
+  void MoveObsoleteMethod(ArtMethod* old_method, ArtMethod* new_method)
+      REQUIRES(!lock_) REQUIRES(Locks::mutator_lock_);
+
  private:
   // Take ownership of maps.
   JitCodeCache(MemMap* code_map,
diff --git a/runtime/jit/profiling_info.h b/runtime/jit/profiling_info.h
index 9902bb5..9fbf2e3 100644
--- a/runtime/jit/profiling_info.h
+++ b/runtime/jit/profiling_info.h
@@ -128,7 +128,9 @@
   const uint32_t number_of_inline_caches_;
 
   // Method this profiling info is for.
-  ArtMethod* const method_;
+  // Not 'const' as JVMTI introduces obsolete methods that we implement by creating new ArtMethods.
+  // See JitCodeCache::MoveObsoleteMethod.
+  ArtMethod* method_;
 
   // Whether the ArtMethod is currently being compiled. This flag
   // is implicitly guarded by the JIT code cache lock.
diff --git a/runtime/method_handles-inl.h b/runtime/method_handles-inl.h
index 1240792..08b8ad9 100644
--- a/runtime/method_handles-inl.h
+++ b/runtime/method_handles-inl.h
@@ -134,36 +134,6 @@
   return true;
 }
 
-template <bool is_range>
-bool ConvertAndCopyArgumentsFromCallerFrame(Thread* self,
-                                            Handle<mirror::MethodType> callsite_type,
-                                            Handle<mirror::MethodType> callee_type,
-                                            const ShadowFrame& caller_frame,
-                                            uint32_t first_src_reg,
-                                            uint32_t first_dest_reg,
-                                            const uint32_t (&arg)[Instruction::kMaxVarArgRegs],
-                                            ShadowFrame* callee_frame)
-    REQUIRES_SHARED(Locks::mutator_lock_) {
-  ObjPtr<mirror::ObjectArray<mirror::Class>> from_types(callsite_type->GetPTypes());
-  ObjPtr<mirror::ObjectArray<mirror::Class>> to_types(callee_type->GetPTypes());
-
-  const int32_t num_method_params = from_types->GetLength();
-  if (to_types->GetLength() != num_method_params) {
-    ThrowWrongMethodTypeException(callee_type.Get(), callsite_type.Get());
-    return false;
-  }
-
-  ShadowFrameGetter<is_range> getter(first_src_reg, arg, caller_frame);
-  ShadowFrameSetter setter(callee_frame, first_dest_reg);
-
-  return PerformConversions<ShadowFrameGetter<is_range>, ShadowFrameSetter>(self,
-                                                                            callsite_type,
-                                                                            callee_type,
-                                                                            &getter,
-                                                                            &setter,
-                                                                            num_method_params);
-}
-
 }  // namespace art
 
 #endif  // ART_RUNTIME_METHOD_HANDLES_INL_H_
diff --git a/runtime/method_handles.cc b/runtime/method_handles.cc
index da510ce..99886e5 100644
--- a/runtime/method_handles.cc
+++ b/runtime/method_handles.cc
@@ -18,8 +18,12 @@
 
 #include "android-base/stringprintf.h"
 
+#include "common_dex_operations.h"
 #include "jvalue.h"
 #include "jvalue-inl.h"
+#include "mirror/emulated_stack_frame.h"
+#include "mirror/method_handle_impl.h"
+#include "mirror/method_type.h"
 #include "reflection.h"
 #include "reflection-inl.h"
 #include "well_known_classes.h"
@@ -282,4 +286,822 @@
   }
 }
 
+namespace {
+
+template <bool is_range>
+inline void CopyArgumentsFromCallerFrame(const ShadowFrame& caller_frame,
+                                         ShadowFrame* callee_frame,
+                                         const uint32_t (&args)[Instruction::kMaxVarArgRegs],
+                                         uint32_t first_arg,
+                                         const size_t first_dst_reg,
+                                         const size_t num_regs)
+    REQUIRES_SHARED(Locks::mutator_lock_) {
+  for (size_t i = 0; i < num_regs; ++i) {
+    size_t dst_reg = first_dst_reg + i;
+    size_t src_reg = is_range ? (first_arg + i) : args[i];
+    // Uint required, so that sign extension does not make this wrong on 64-bit systems
+    uint32_t src_value = caller_frame.GetVReg(src_reg);
+    ObjPtr<mirror::Object> o = caller_frame.GetVRegReference<kVerifyNone>(src_reg);
+    // If both register locations contains the same value, the register probably holds a reference.
+    // Note: As an optimization, non-moving collectors leave a stale reference value
+    // in the references array even after the original vreg was overwritten to a non-reference.
+    if (src_value == reinterpret_cast<uintptr_t>(o.Ptr())) {
+      callee_frame->SetVRegReference(dst_reg, o.Ptr());
+    } else {
+      callee_frame->SetVReg(dst_reg, src_value);
+    }
+  }
+}
+
+template <bool is_range>
+inline bool ConvertAndCopyArgumentsFromCallerFrame(
+    Thread* self,
+    Handle<mirror::MethodType> callsite_type,
+    Handle<mirror::MethodType> callee_type,
+    const ShadowFrame& caller_frame,
+    const uint32_t (&args)[Instruction::kMaxVarArgRegs],
+    uint32_t first_arg,
+    uint32_t first_dst_reg,
+    ShadowFrame* callee_frame)
+    REQUIRES_SHARED(Locks::mutator_lock_) {
+  ObjPtr<mirror::ObjectArray<mirror::Class>> from_types(callsite_type->GetPTypes());
+  ObjPtr<mirror::ObjectArray<mirror::Class>> to_types(callee_type->GetPTypes());
+
+  const int32_t num_method_params = from_types->GetLength();
+  if (to_types->GetLength() != num_method_params) {
+    ThrowWrongMethodTypeException(callee_type.Get(), callsite_type.Get());
+    return false;
+  }
+
+  ShadowFrameGetter<is_range> getter(first_arg, args, caller_frame);
+  ShadowFrameSetter setter(callee_frame, first_dst_reg);
+
+  return PerformConversions<ShadowFrameGetter<is_range>, ShadowFrameSetter>(self,
+                                                                            callsite_type,
+                                                                            callee_type,
+                                                                            &getter,
+                                                                            &setter,
+                                                                            num_method_params);
+}
+
+inline bool IsMethodHandleInvokeExact(const ArtMethod* const method) {
+  if (method == jni::DecodeArtMethod(WellKnownClasses::java_lang_invoke_MethodHandle_invokeExact)) {
+    return true;
+  } else {
+    DCHECK_EQ(method, jni::DecodeArtMethod(WellKnownClasses::java_lang_invoke_MethodHandle_invoke));
+    return false;
+  }
+}
+
+inline bool IsInvoke(const mirror::MethodHandle::Kind handle_kind) {
+  return handle_kind <= mirror::MethodHandle::Kind::kLastInvokeKind;
+}
+
+inline bool IsInvokeTransform(const mirror::MethodHandle::Kind handle_kind) {
+  return (handle_kind == mirror::MethodHandle::Kind::kInvokeTransform
+          || handle_kind == mirror::MethodHandle::Kind::kInvokeCallSiteTransform);
+}
+
+inline bool IsFieldAccess(mirror::MethodHandle::Kind handle_kind) {
+  return (handle_kind >= mirror::MethodHandle::Kind::kFirstAccessorKind
+          && handle_kind <= mirror::MethodHandle::Kind::kLastAccessorKind);
+}
+
+// Calculate the number of ins for a proxy or native method, where we
+// can't just look at the code item.
+static inline size_t GetInsForProxyOrNativeMethod(ArtMethod* method)
+    REQUIRES_SHARED(Locks::mutator_lock_) {
+  DCHECK(method->IsNative() || method->IsProxyMethod());
+
+  method = method->GetInterfaceMethodIfProxy(kRuntimePointerSize);
+  size_t num_ins = 0;
+  // Separate accounting for the receiver, which isn't a part of the
+  // shorty.
+  if (!method->IsStatic()) {
+    ++num_ins;
+  }
+
+  uint32_t shorty_len = 0;
+  const char* shorty = method->GetShorty(&shorty_len);
+  for (size_t i = 1; i < shorty_len; ++i) {
+    const char c = shorty[i];
+    ++num_ins;
+    if (c == 'J' || c == 'D') {
+      ++num_ins;
+    }
+  }
+
+  return num_ins;
+}
+
+// Returns true iff. the callsite type for a polymorphic invoke is transformer
+// like, i.e that it has a single input argument whose type is
+// dalvik.system.EmulatedStackFrame.
+static inline bool IsCallerTransformer(Handle<mirror::MethodType> callsite_type)
+    REQUIRES_SHARED(Locks::mutator_lock_) {
+  ObjPtr<mirror::ObjectArray<mirror::Class>> param_types(callsite_type->GetPTypes());
+  if (param_types->GetLength() == 1) {
+    ObjPtr<mirror::Class> param(param_types->GetWithoutChecks(0));
+    return param == WellKnownClasses::ToClass(WellKnownClasses::dalvik_system_EmulatedStackFrame);
+  }
+
+  return false;
+}
+
+template <bool is_range>
+static inline bool DoCallPolymorphic(ArtMethod* called_method,
+                                     Handle<mirror::MethodType> callsite_type,
+                                     Handle<mirror::MethodType> target_type,
+                                     Thread* self,
+                                     ShadowFrame& shadow_frame,
+                                     const uint32_t (&args)[Instruction::kMaxVarArgRegs],
+                                     uint32_t first_arg,
+                                     JValue* result,
+                                     const mirror::MethodHandle::Kind handle_kind)
+    REQUIRES_SHARED(Locks::mutator_lock_) {
+  // Compute method information.
+  const DexFile::CodeItem* code_item = called_method->GetCodeItem();
+
+  // Number of registers for the callee's call frame. Note that for non-exact
+  // invokes, we always derive this information from the callee method. We
+  // cannot guarantee during verification that the number of registers encoded
+  // in the invoke is equal to the number of ins for the callee. This is because
+  // some transformations (such as boxing a long -> Long or wideining an
+  // int -> long will change that number.
+  uint16_t num_regs;
+  size_t num_input_regs;
+  size_t first_dest_reg;
+  if (LIKELY(code_item != nullptr)) {
+    num_regs = code_item->registers_size_;
+    first_dest_reg = num_regs - code_item->ins_size_;
+    num_input_regs = code_item->ins_size_;
+    // Parameter registers go at the end of the shadow frame.
+    DCHECK_NE(first_dest_reg, (size_t)-1);
+  } else {
+    // No local regs for proxy and native methods.
+    DCHECK(called_method->IsNative() || called_method->IsProxyMethod());
+    num_regs = num_input_regs = GetInsForProxyOrNativeMethod(called_method);
+    first_dest_reg = 0;
+  }
+
+  // Allocate shadow frame on the stack.
+  ShadowFrameAllocaUniquePtr shadow_frame_unique_ptr =
+      CREATE_SHADOW_FRAME(num_regs, &shadow_frame, called_method, /* dex pc */ 0);
+  ShadowFrame* new_shadow_frame = shadow_frame_unique_ptr.get();
+
+  // Whether this polymorphic invoke was issued by a transformer method.
+  bool is_caller_transformer = false;
+  // Thread might be suspended during PerformArgumentConversions due to the
+  // allocations performed during boxing.
+  {
+    ScopedStackedShadowFramePusher pusher(
+        self, new_shadow_frame, StackedShadowFrameType::kShadowFrameUnderConstruction);
+    if (callsite_type->IsExactMatch(target_type.Get())) {
+      // This is an exact invoke, we can take the fast path of just copying all
+      // registers without performing any argument conversions.
+      CopyArgumentsFromCallerFrame<is_range>(shadow_frame,
+                                             new_shadow_frame,
+                                             args,
+                                             first_arg,
+                                             first_dest_reg,
+                                             num_input_regs);
+    } else {
+      // This includes the case where we're entering this invoke-polymorphic
+      // from a transformer method. In that case, the callsite_type will contain
+      // a single argument of type dalvik.system.EmulatedStackFrame. In that
+      // case, we'll have to unmarshal the EmulatedStackFrame into the
+      // new_shadow_frame and perform argument conversions on it.
+      if (IsCallerTransformer(callsite_type)) {
+        is_caller_transformer = true;
+        // The emulated stack frame is the first and only argument when we're coming
+        // through from a transformer.
+        size_t first_arg_register = (is_range) ? first_arg : args[0];
+        ObjPtr<mirror::EmulatedStackFrame> emulated_stack_frame(
+            reinterpret_cast<mirror::EmulatedStackFrame*>(
+                shadow_frame.GetVRegReference(first_arg_register)));
+        if (!emulated_stack_frame->WriteToShadowFrame(self,
+                                                      target_type,
+                                                      first_dest_reg,
+                                                      new_shadow_frame)) {
+          DCHECK(self->IsExceptionPending());
+          result->SetL(0);
+          return false;
+        }
+      } else if (!ConvertAndCopyArgumentsFromCallerFrame<is_range>(self,
+                                                                   callsite_type,
+                                                                   target_type,
+                                                                   shadow_frame,
+                                                                   args,
+                                                                   first_arg,
+                                                                   first_dest_reg,
+                                                                   new_shadow_frame)) {
+        DCHECK(self->IsExceptionPending());
+        result->SetL(0);
+        return false;
+      }
+    }
+  }
+
+  // See TODO in DoInvokePolymorphic : We need to perform this dynamic, receiver
+  // based dispatch right before we perform the actual call, because the
+  // receiver isn't known very early.
+  if (handle_kind == mirror::MethodHandle::Kind::kInvokeVirtual ||
+      handle_kind == mirror::MethodHandle::Kind::kInvokeInterface) {
+    ObjPtr<mirror::Object> receiver(new_shadow_frame->GetVRegReference(first_dest_reg));
+    ObjPtr<mirror::Class> declaring_class(called_method->GetDeclaringClass());
+    // Verify that _vRegC is an object reference and of the type expected by
+    // the receiver.
+    if (!VerifyObjectIsClass(receiver, declaring_class)) {
+      DCHECK(self->IsExceptionPending());
+      return false;
+    }
+
+    called_method = receiver->GetClass()->FindVirtualMethodForVirtualOrInterface(
+        called_method, kRuntimePointerSize);
+  }
+
+  PerformCall(self, code_item, shadow_frame.GetMethod(), first_dest_reg, new_shadow_frame, result);
+  if (self->IsExceptionPending()) {
+    return false;
+  }
+
+  // If the caller of this signature polymorphic method was a transformer,
+  // we need to copy the result back out to the emulated stack frame.
+  if (is_caller_transformer) {
+    StackHandleScope<2> hs(self);
+    size_t first_callee_register = is_range ? (first_arg) : args[0];
+    Handle<mirror::EmulatedStackFrame> emulated_stack_frame(
+        hs.NewHandle(reinterpret_cast<mirror::EmulatedStackFrame*>(
+            shadow_frame.GetVRegReference(first_callee_register))));
+    Handle<mirror::MethodType> emulated_stack_type(hs.NewHandle(emulated_stack_frame->GetType()));
+    JValue local_result;
+    local_result.SetJ(result->GetJ());
+
+    if (ConvertReturnValue(emulated_stack_type, target_type, &local_result)) {
+      emulated_stack_frame->SetReturnValue(self, local_result);
+      return true;
+    } else {
+      DCHECK(self->IsExceptionPending());
+      return false;
+    }
+  } else {
+    return ConvertReturnValue(callsite_type, target_type, result);
+  }
+}
+
+template <bool is_range>
+static inline bool DoCallTransform(ArtMethod* called_method,
+                                   Handle<mirror::MethodType> callsite_type,
+                                   Handle<mirror::MethodType> callee_type,
+                                   Thread* self,
+                                   ShadowFrame& shadow_frame,
+                                   Handle<mirror::MethodHandleImpl> receiver,
+                                   const uint32_t (&args)[Instruction::kMaxVarArgRegs],
+                                   uint32_t first_arg,
+                                   JValue* result)
+    REQUIRES_SHARED(Locks::mutator_lock_) {
+  // This can be fixed to two, because the method we're calling here
+  // (MethodHandle.transformInternal) doesn't have any locals and the signature
+  // is known :
+  //
+  // private MethodHandle.transformInternal(EmulatedStackFrame sf);
+  //
+  // This means we need only two vregs :
+  // - One for the receiver object.
+  // - One for the only method argument (an EmulatedStackFrame).
+  static constexpr size_t kNumRegsForTransform = 2;
+
+  const DexFile::CodeItem* code_item = called_method->GetCodeItem();
+  DCHECK(code_item != nullptr);
+  DCHECK_EQ(kNumRegsForTransform, code_item->registers_size_);
+  DCHECK_EQ(kNumRegsForTransform, code_item->ins_size_);
+
+  ShadowFrameAllocaUniquePtr shadow_frame_unique_ptr =
+      CREATE_SHADOW_FRAME(kNumRegsForTransform, &shadow_frame, called_method, /* dex pc */ 0);
+  ShadowFrame* new_shadow_frame = shadow_frame_unique_ptr.get();
+
+  StackHandleScope<1> hs(self);
+  MutableHandle<mirror::EmulatedStackFrame> sf(hs.NewHandle<mirror::EmulatedStackFrame>(nullptr));
+  if (IsCallerTransformer(callsite_type)) {
+    // If we're entering this transformer from another transformer, we can pass
+    // through the handle directly to the callee, instead of having to
+    // instantiate a new stack frame based on the shadow frame.
+    size_t first_callee_register = is_range ? first_arg : args[0];
+    sf.Assign(reinterpret_cast<mirror::EmulatedStackFrame*>(
+        shadow_frame.GetVRegReference(first_callee_register)));
+  } else {
+    sf.Assign(mirror::EmulatedStackFrame::CreateFromShadowFrameAndArgs<is_range>(self,
+                                                                                 callsite_type,
+                                                                                 callee_type,
+                                                                                 shadow_frame,
+                                                                                 first_arg,
+                                                                                 args));
+
+    // Something went wrong while creating the emulated stack frame, we should
+    // throw the pending exception.
+    if (sf.Get() == nullptr) {
+      DCHECK(self->IsExceptionPending());
+      return false;
+    }
+  }
+
+  new_shadow_frame->SetVRegReference(0, receiver.Get());
+  new_shadow_frame->SetVRegReference(1, sf.Get());
+
+  PerformCall(self,
+              code_item,
+              shadow_frame.GetMethod(),
+              0 /* first destination register */,
+              new_shadow_frame,
+              result);
+  if (self->IsExceptionPending()) {
+    return false;
+  }
+
+  // If the called transformer method we called has returned a value, then we
+  // need to copy it back to |result|.
+  sf->GetReturnValue(self, result);
+  return ConvertReturnValue(callsite_type, callee_type, result);
+}
+
+inline static ObjPtr<mirror::Class> GetAndInitializeDeclaringClass(Thread* self, ArtField* field)
+    REQUIRES_SHARED(Locks::mutator_lock_) {
+  // Method handle invocations on static fields should ensure class is
+  // initialized. This usually happens when an instance is constructed
+  // or class members referenced, but this is not guaranteed when
+  // looking up method handles.
+  ObjPtr<mirror::Class> klass = field->GetDeclaringClass();
+  if (UNLIKELY(!klass->IsInitialized())) {
+    StackHandleScope<1> hs(self);
+    HandleWrapperObjPtr<mirror::Class> h(hs.NewHandleWrapper(&klass));
+    if (!Runtime::Current()->GetClassLinker()->EnsureInitialized(self, h, true, true)) {
+      DCHECK(self->IsExceptionPending());
+      return nullptr;
+    }
+  }
+  return klass;
+}
+
+template <bool is_range>
+bool DoInvokePolymorphicUnchecked(Thread* self,
+                                  ShadowFrame& shadow_frame,
+                                  Handle<mirror::MethodHandleImpl> method_handle,
+                                  Handle<mirror::MethodType> callsite_type,
+                                  const uint32_t (&args)[Instruction::kMaxVarArgRegs],
+                                  uint32_t first_arg,
+                                  JValue* result)
+  REQUIRES_SHARED(Locks::mutator_lock_) {
+  StackHandleScope<1> hs(self);
+  Handle<mirror::MethodType> handle_type(hs.NewHandle(method_handle->GetMethodType()));
+  const mirror::MethodHandle::Kind handle_kind = method_handle->GetHandleKind();
+  if (IsInvoke(handle_kind)) {
+    // Get the method we're actually invoking along with the kind of
+    // invoke that is desired. We don't need to perform access checks at this
+    // point because they would have been performed on our behalf at the point
+    // of creation of the method handle.
+    ArtMethod* called_method = method_handle->GetTargetMethod();
+    CHECK(called_method != nullptr);
+
+    if (handle_kind == mirror::MethodHandle::Kind::kInvokeVirtual ||
+        handle_kind == mirror::MethodHandle::Kind::kInvokeInterface) {
+      // TODO: Unfortunately, we have to postpone dynamic receiver based checks
+      // because the receiver might be cast or might come from an emulated stack
+      // frame, which means that it is unknown at this point. We perform these
+      // checks inside DoCallPolymorphic right before we do the actual invoke.
+    } else if (handle_kind == mirror::MethodHandle::Kind::kInvokeDirect) {
+      // String constructors are a special case, they are replaced with StringFactory
+      // methods.
+      if (called_method->IsConstructor() && called_method->GetDeclaringClass()->IsStringClass()) {
+        DCHECK(handle_type->GetRType()->IsStringClass());
+        called_method = WellKnownClasses::StringInitToStringFactory(called_method);
+      }
+    } else if (handle_kind == mirror::MethodHandle::Kind::kInvokeSuper) {
+      ObjPtr<mirror::Class> declaring_class = called_method->GetDeclaringClass();
+
+      // Note that we're not dynamically dispatching on the type of the receiver
+      // here. We use the static type of the "receiver" object that we've
+      // recorded in the method handle's type, which will be the same as the
+      // special caller that was specified at the point of lookup.
+      ObjPtr<mirror::Class> referrer_class = handle_type->GetPTypes()->Get(0);
+      if (!declaring_class->IsInterface()) {
+        ObjPtr<mirror::Class> super_class = referrer_class->GetSuperClass();
+        uint16_t vtable_index = called_method->GetMethodIndex();
+        DCHECK(super_class != nullptr);
+        DCHECK(super_class->HasVTable());
+        // Note that super_class is a super of referrer_class and called_method
+        // will always be declared by super_class (or one of its super classes).
+        DCHECK_LT(vtable_index, super_class->GetVTableLength());
+        called_method = super_class->GetVTableEntry(vtable_index, kRuntimePointerSize);
+      } else {
+        called_method = referrer_class->FindVirtualMethodForInterfaceSuper(
+            called_method, kRuntimePointerSize);
+      }
+      CHECK(called_method != nullptr);
+    }
+    if (IsInvokeTransform(handle_kind)) {
+      // There are two cases here - method handles representing regular
+      // transforms and those representing call site transforms. Method
+      // handles for call site transforms adapt their MethodType to match
+      // the call site. For these, the |callee_type| is the same as the
+      // |callsite_type|. The VarargsCollector is such a tranform, its
+      // method type depends on the call site, ie. x(a) or x(a, b), or
+      // x(a, b, c). The VarargsCollector invokes a variable arity method
+      // with the arity arguments in an array.
+      Handle<mirror::MethodType> callee_type =
+          (handle_kind == mirror::MethodHandle::Kind::kInvokeCallSiteTransform) ? callsite_type
+          : handle_type;
+      return DoCallTransform<is_range>(called_method,
+                                       callsite_type,
+                                       callee_type,
+                                       self,
+                                       shadow_frame,
+                                       method_handle /* receiver */,
+                                       args,
+                                       first_arg,
+                                       result);
+
+    } else {
+      return DoCallPolymorphic<is_range>(called_method,
+                                         callsite_type,
+                                         handle_type,
+                                         self,
+                                         shadow_frame,
+                                         args,
+                                         first_arg,
+                                         result,
+                                         handle_kind);
+    }
+  } else {
+    LOG(FATAL) << "Unreachable: " << handle_kind;
+    UNREACHABLE();
+  }
+}
+
+// Helper for getters in invoke-polymorphic.
+inline static void DoFieldGetForInvokePolymorphic(Thread* self,
+                                                  const ShadowFrame& shadow_frame,
+                                                  ObjPtr<mirror::Object>& obj,
+                                                  ArtField* field,
+                                                  Primitive::Type field_type,
+                                                  JValue* result)
+    REQUIRES_SHARED(Locks::mutator_lock_) {
+  switch (field_type) {
+    case Primitive::kPrimBoolean:
+      DoFieldGetCommon<Primitive::kPrimBoolean>(self, shadow_frame, obj, field, result);
+      break;
+    case Primitive::kPrimByte:
+      DoFieldGetCommon<Primitive::kPrimByte>(self, shadow_frame, obj, field, result);
+      break;
+    case Primitive::kPrimChar:
+      DoFieldGetCommon<Primitive::kPrimChar>(self, shadow_frame, obj, field, result);
+      break;
+    case Primitive::kPrimShort:
+      DoFieldGetCommon<Primitive::kPrimShort>(self, shadow_frame, obj, field, result);
+      break;
+    case Primitive::kPrimInt:
+      DoFieldGetCommon<Primitive::kPrimInt>(self, shadow_frame, obj, field, result);
+      break;
+    case Primitive::kPrimLong:
+      DoFieldGetCommon<Primitive::kPrimLong>(self, shadow_frame, obj, field, result);
+      break;
+    case Primitive::kPrimFloat:
+      DoFieldGetCommon<Primitive::kPrimInt>(self, shadow_frame, obj, field, result);
+      break;
+    case Primitive::kPrimDouble:
+      DoFieldGetCommon<Primitive::kPrimLong>(self, shadow_frame, obj, field, result);
+      break;
+    case Primitive::kPrimNot:
+      DoFieldGetCommon<Primitive::kPrimNot>(self, shadow_frame, obj, field, result);
+      break;
+    case Primitive::kPrimVoid:
+      LOG(FATAL) << "Unreachable: " << field_type;
+      UNREACHABLE();
+  }
+}
+
+// Helper for setters in invoke-polymorphic.
+template <bool do_assignability_check>
+inline bool DoFieldPutForInvokePolymorphic(Thread* self,
+                                           ShadowFrame& shadow_frame,
+                                           ObjPtr<mirror::Object>& obj,
+                                           ArtField* field,
+                                           Primitive::Type field_type,
+                                           const JValue& value)
+    REQUIRES_SHARED(Locks::mutator_lock_) {
+  static const bool kTransaction = false;
+  switch (field_type) {
+    case Primitive::kPrimBoolean:
+      return DoFieldPutCommon<Primitive::kPrimBoolean, do_assignability_check, kTransaction>(
+          self, shadow_frame, obj, field, value);
+    case Primitive::kPrimByte:
+      return DoFieldPutCommon<Primitive::kPrimByte, do_assignability_check, kTransaction>(
+          self, shadow_frame, obj, field, value);
+    case Primitive::kPrimChar:
+      return DoFieldPutCommon<Primitive::kPrimChar, do_assignability_check, kTransaction>(
+          self, shadow_frame, obj, field, value);
+    case Primitive::kPrimShort:
+      return DoFieldPutCommon<Primitive::kPrimShort, do_assignability_check, kTransaction>(
+          self, shadow_frame, obj, field, value);
+    case Primitive::kPrimInt:
+    case Primitive::kPrimFloat:
+      return DoFieldPutCommon<Primitive::kPrimInt, do_assignability_check, kTransaction>(
+          self, shadow_frame, obj, field, value);
+    case Primitive::kPrimLong:
+    case Primitive::kPrimDouble:
+      return DoFieldPutCommon<Primitive::kPrimLong, do_assignability_check, kTransaction>(
+          self, shadow_frame, obj, field, value);
+    case Primitive::kPrimNot:
+      return DoFieldPutCommon<Primitive::kPrimNot, do_assignability_check, kTransaction>(
+          self, shadow_frame, obj, field, value);
+    case Primitive::kPrimVoid:
+      LOG(FATAL) << "Unreachable: " << field_type;
+      UNREACHABLE();
+  }
+}
+
+static JValue GetValueFromShadowFrame(const ShadowFrame& shadow_frame,
+                                      Primitive::Type field_type,
+                                      uint32_t vreg)
+    REQUIRES_SHARED(Locks::mutator_lock_) {
+  JValue field_value;
+  switch (field_type) {
+    case Primitive::kPrimBoolean:
+      field_value.SetZ(static_cast<uint8_t>(shadow_frame.GetVReg(vreg)));
+      break;
+    case Primitive::kPrimByte:
+      field_value.SetB(static_cast<int8_t>(shadow_frame.GetVReg(vreg)));
+      break;
+    case Primitive::kPrimChar:
+      field_value.SetC(static_cast<uint16_t>(shadow_frame.GetVReg(vreg)));
+      break;
+    case Primitive::kPrimShort:
+      field_value.SetS(static_cast<int16_t>(shadow_frame.GetVReg(vreg)));
+      break;
+    case Primitive::kPrimInt:
+    case Primitive::kPrimFloat:
+      field_value.SetI(shadow_frame.GetVReg(vreg));
+      break;
+    case Primitive::kPrimLong:
+    case Primitive::kPrimDouble:
+      field_value.SetJ(shadow_frame.GetVRegLong(vreg));
+      break;
+    case Primitive::kPrimNot:
+      field_value.SetL(shadow_frame.GetVRegReference(vreg));
+      break;
+    case Primitive::kPrimVoid:
+      LOG(FATAL) << "Unreachable: " << field_type;
+      UNREACHABLE();
+  }
+  return field_value;
+}
+
+template <bool is_range, bool do_conversions, bool do_assignability_check>
+bool DoInvokePolymorphicFieldAccess(Thread* self,
+                                    ShadowFrame& shadow_frame,
+                                    Handle<mirror::MethodHandleImpl> method_handle,
+                                    Handle<mirror::MethodType> callsite_type,
+                                    const uint32_t (&args)[Instruction::kMaxVarArgRegs],
+                                    uint32_t first_arg,
+                                    JValue* result)
+    REQUIRES_SHARED(Locks::mutator_lock_) {
+  StackHandleScope<1> hs(self);
+  Handle<mirror::MethodType> handle_type(hs.NewHandle(method_handle->GetMethodType()));
+  const mirror::MethodHandle::Kind handle_kind = method_handle->GetHandleKind();
+  ArtField* field = method_handle->GetTargetField();
+  Primitive::Type field_type = field->GetTypeAsPrimitiveType();
+
+  switch (handle_kind) {
+    case mirror::MethodHandle::kInstanceGet: {
+      size_t obj_reg = is_range ? first_arg : args[0];
+      ObjPtr<mirror::Object> obj = shadow_frame.GetVRegReference(obj_reg);
+      DoFieldGetForInvokePolymorphic(self, shadow_frame, obj, field, field_type, result);
+      if (do_conversions && !ConvertReturnValue(callsite_type, handle_type, result)) {
+        DCHECK(self->IsExceptionPending());
+        return false;
+      }
+      return true;
+    }
+    case mirror::MethodHandle::kStaticGet: {
+      ObjPtr<mirror::Object> obj = GetAndInitializeDeclaringClass(self, field);
+      if (obj == nullptr) {
+        DCHECK(self->IsExceptionPending());
+        return false;
+      }
+      DoFieldGetForInvokePolymorphic(self, shadow_frame, obj, field, field_type, result);
+      if (do_conversions && !ConvertReturnValue(callsite_type, handle_type, result)) {
+        DCHECK(self->IsExceptionPending());
+        return false;
+      }
+      return true;
+    }
+    case mirror::MethodHandle::kInstancePut: {
+      size_t obj_reg = is_range ? first_arg : args[0];
+      size_t value_reg = is_range ? (first_arg + 1) : args[1];
+      JValue value = GetValueFromShadowFrame(shadow_frame, field_type, value_reg);
+      if (do_conversions && !ConvertArgumentValue(callsite_type, handle_type, 1, &value)) {
+        DCHECK(self->IsExceptionPending());
+        return false;
+      }
+      ObjPtr<mirror::Object> obj = shadow_frame.GetVRegReference(obj_reg);
+      return DoFieldPutForInvokePolymorphic<do_assignability_check>(self,
+                                                                    shadow_frame,
+                                                                    obj,
+                                                                    field,
+                                                                    field_type,
+                                                                    value);
+    }
+    case mirror::MethodHandle::kStaticPut: {
+      ObjPtr<mirror::Object> obj = GetAndInitializeDeclaringClass(self, field);
+      if (obj == nullptr) {
+        DCHECK(self->IsExceptionPending());
+        return false;
+      }
+      size_t value_reg = is_range ? first_arg : args[0];
+      JValue value = GetValueFromShadowFrame(shadow_frame, field_type, value_reg);
+      if (do_conversions && !ConvertArgumentValue(callsite_type, handle_type, 0, &value)) {
+        DCHECK(self->IsExceptionPending());
+        return false;
+      }
+      return DoFieldPutForInvokePolymorphic<do_assignability_check>(self,
+                                                                    shadow_frame,
+                                                                    obj,
+                                                                    field,
+                                                                    field_type,
+                                                                    value);
+    }
+    default:
+      LOG(FATAL) << "Unreachable: " << handle_kind;
+      UNREACHABLE();
+  }
+}
+
+template <bool is_range, bool do_assignability_check>
+static inline bool DoInvokePolymorphicNonExact(Thread* self,
+                                               ShadowFrame& shadow_frame,
+                                               Handle<mirror::MethodHandleImpl> method_handle,
+                                               Handle<mirror::MethodType> callsite_type,
+                                               const uint32_t (&args)[Instruction::kMaxVarArgRegs],
+                                               uint32_t first_arg,
+                                               JValue* result)
+    REQUIRES_SHARED(Locks::mutator_lock_) {
+  const mirror::MethodHandle::Kind handle_kind = method_handle->GetHandleKind();
+  ObjPtr<mirror::MethodType> handle_type(method_handle->GetMethodType());
+  CHECK(handle_type != nullptr);
+
+  if (!IsInvokeTransform(handle_kind)) {
+    if (UNLIKELY(!IsCallerTransformer(callsite_type) &&
+                 !callsite_type->IsConvertible(handle_type.Ptr()))) {
+      ThrowWrongMethodTypeException(handle_type.Ptr(), callsite_type.Get());
+      return false;
+    }
+  }
+
+  if (IsFieldAccess(handle_kind)) {
+    if (UNLIKELY(callsite_type->IsExactMatch(handle_type.Ptr()))) {
+      const bool do_convert = false;
+      return DoInvokePolymorphicFieldAccess<is_range, do_convert, do_assignability_check>(
+          self,
+          shadow_frame,
+          method_handle,
+          callsite_type,
+          args,
+          first_arg,
+          result);
+    } else {
+      const bool do_convert = true;
+      return DoInvokePolymorphicFieldAccess<is_range, do_convert, do_assignability_check>(
+          self,
+          shadow_frame,
+          method_handle,
+          callsite_type,
+          args,
+          first_arg,
+          result);
+    }
+  }
+
+  if (UNLIKELY(callsite_type->IsExactMatch(handle_type.Ptr()))) {
+    return DoInvokePolymorphicUnchecked<is_range>(self,
+                                                  shadow_frame,
+                                                  method_handle,
+                                                  callsite_type,
+                                                  args,
+                                                  first_arg,
+                                                  result);
+  } else {
+    return DoInvokePolymorphicUnchecked<is_range>(self,
+                                                  shadow_frame,
+                                                  method_handle,
+                                                  callsite_type,
+                                                  args,
+                                                  first_arg,
+                                                  result);
+  }
+}
+
+template <bool is_range, bool do_assignability_check>
+bool DoInvokePolymorphicExact(Thread* self,
+                              ShadowFrame& shadow_frame,
+                              Handle<mirror::MethodHandleImpl> method_handle,
+                              Handle<mirror::MethodType> callsite_type,
+                              const uint32_t (&args)[Instruction::kMaxVarArgRegs],
+                              uint32_t first_arg,
+                              JValue* result)
+    REQUIRES_SHARED(Locks::mutator_lock_) {
+  // We need to check the nominal type of the handle in addition to the
+  // real type. The "nominal" type is present when MethodHandle.asType is
+  // called any handle, and results in the declared type of the handle
+  // changing.
+  ObjPtr<mirror::MethodType> nominal_type(method_handle->GetNominalType());
+  if (UNLIKELY(nominal_type != nullptr)) {
+    if (UNLIKELY(!callsite_type->IsExactMatch(nominal_type.Ptr()))) {
+      ThrowWrongMethodTypeException(nominal_type.Ptr(), callsite_type.Get());
+      return false;
+    }
+    return DoInvokePolymorphicNonExact<is_range, do_assignability_check>(self,
+                                                                         shadow_frame,
+                                                                         method_handle,
+                                                                         callsite_type,
+                                                                         args,
+                                                                         first_arg,
+                                                                         result);
+  }
+
+  ObjPtr<mirror::MethodType> handle_type(method_handle->GetMethodType());
+  if (UNLIKELY(!callsite_type->IsExactMatch(handle_type.Ptr()))) {
+    ThrowWrongMethodTypeException(handle_type.Ptr(), callsite_type.Get());
+    return false;
+  }
+
+  const mirror::MethodHandle::Kind handle_kind = method_handle->GetHandleKind();
+  if (IsFieldAccess(handle_kind)) {
+    const bool do_convert = false;
+    return DoInvokePolymorphicFieldAccess<is_range, do_convert, do_assignability_check>(
+        self,
+        shadow_frame,
+        method_handle,
+        callsite_type,
+        args,
+        first_arg,
+        result);
+  }
+
+  return DoInvokePolymorphicUnchecked<is_range>(self,
+                                                shadow_frame,
+                                                method_handle,
+                                                callsite_type,
+                                                args,
+                                                first_arg,
+                                                result);
+}
+
+}  // namespace
+
+template <bool is_range, bool do_assignability_check>
+bool DoInvokePolymorphic(Thread* self,
+                         ArtMethod* invoke_method,
+                         ShadowFrame& shadow_frame,
+                         Handle<mirror::MethodHandleImpl> method_handle,
+                         Handle<mirror::MethodType> callsite_type,
+                         const uint32_t (&args)[Instruction::kMaxVarArgRegs],
+                         uint32_t first_arg,
+                         JValue* result)
+    REQUIRES_SHARED(Locks::mutator_lock_) {
+  if (IsMethodHandleInvokeExact(invoke_method)) {
+    return DoInvokePolymorphicExact<is_range, do_assignability_check>(self,
+                                                                      shadow_frame,
+                                                                      method_handle,
+                                                                      callsite_type,
+                                                                      args,
+                                                                      first_arg,
+                                                                      result);
+  } else {
+    return DoInvokePolymorphicNonExact<is_range, do_assignability_check>(self,
+                                                                         shadow_frame,
+                                                                         method_handle,
+                                                                         callsite_type,
+                                                                         args,
+                                                                         first_arg,
+                                                                         result);
+  }
+}
+
+#define EXPLICIT_DO_INVOKE_POLYMORPHIC_TEMPLATE_DECL(_is_range, _do_assignability_check) \
+template REQUIRES_SHARED(Locks::mutator_lock_)                                           \
+bool DoInvokePolymorphic<_is_range, _do_assignability_check>(                            \
+    Thread* self,                                                                        \
+    ArtMethod* invoke_method,                                                            \
+    ShadowFrame& shadow_frame,                                                           \
+    Handle<mirror::MethodHandleImpl> method_handle,                                      \
+    Handle<mirror::MethodType> callsite_type,                                            \
+    const uint32_t (&args)[Instruction::kMaxVarArgRegs],                                 \
+    uint32_t first_arg,                                                                  \
+    JValue* result)
+
+EXPLICIT_DO_INVOKE_POLYMORPHIC_TEMPLATE_DECL(true, true);
+EXPLICIT_DO_INVOKE_POLYMORPHIC_TEMPLATE_DECL(true, false);
+EXPLICIT_DO_INVOKE_POLYMORPHIC_TEMPLATE_DECL(false, true);
+EXPLICIT_DO_INVOKE_POLYMORPHIC_TEMPLATE_DECL(false, false);
+#undef EXPLICIT_DO_INVOKE_POLYMORPHIC_TEMPLATE_DECL
+
 }  // namespace art
diff --git a/runtime/method_handles.h b/runtime/method_handles.h
index d0a4902..734d7c7 100644
--- a/runtime/method_handles.h
+++ b/runtime/method_handles.h
@@ -23,48 +23,16 @@
 #include "handle.h"
 #include "jvalue.h"
 #include "mirror/class.h"
-#include "mirror/method_type.h"
 
 namespace art {
 
 namespace mirror {
+  class MethodHandleImpl;
   class MethodType;
-}
+}  // mirror
 
 class ShadowFrame;
 
-// Defines the behaviour of a given method handle. The behaviour
-// of a handle of a given kind is identical to the dex bytecode behaviour
-// of the equivalent instruction.
-//
-// NOTE: These must be kept in sync with the constants defined in
-// java.lang.invoke.MethodHandle.
-enum MethodHandleKind {
-  kInvokeVirtual = 0,
-  kInvokeSuper,
-  kInvokeDirect,
-  kInvokeStatic,
-  kInvokeInterface,
-  kInvokeTransform,
-  kInvokeCallSiteTransform,
-  kInstanceGet,
-  kInstancePut,
-  kStaticGet,
-  kStaticPut,
-  kLastValidKind = kStaticPut,
-  kLastInvokeKind = kInvokeCallSiteTransform
-};
-
-// Whether the given method handle kind is some variant of an invoke.
-inline bool IsInvoke(const MethodHandleKind handle_kind) {
-  return handle_kind <= kLastInvokeKind;
-}
-
-// Whether the given method handle kind is some variant of a tranform.
-inline bool IsInvokeTransform(const MethodHandleKind handle_kind) {
-  return handle_kind == kInvokeTransform || handle_kind == kInvokeCallSiteTransform;
-}
-
 // Returns true if there is a possible conversion from |from| to |to|
 // for a MethodHandle parameter.
 bool IsParameterTypeConvertible(ObjPtr<mirror::Class> from,
@@ -158,19 +126,6 @@
                         S* setter,
                         int32_t num_conversions) REQUIRES_SHARED(Locks::mutator_lock_);
 
-// A convenience wrapper around |PerformConversions|, for the case where
-// the setter and getter are both ShadowFrame based.
-template <bool is_range>
-bool ConvertAndCopyArgumentsFromCallerFrame(Thread* self,
-                                            Handle<mirror::MethodType> callsite_type,
-                                            Handle<mirror::MethodType> callee_type,
-                                            const ShadowFrame& caller_frame,
-                                            uint32_t first_src_reg,
-                                            uint32_t first_dest_reg,
-                                            const uint32_t (&arg)[Instruction::kMaxVarArgRegs],
-                                            ShadowFrame* callee_frame)
-    REQUIRES_SHARED(Locks::mutator_lock_);
-
 // A convenience class that allows for iteration through a list of
 // input argument registers |arg| for non-range invokes or a list of
 // consecutive registers starting with a given based for range
@@ -178,7 +133,8 @@
 //
 // This is used to iterate over input arguments while performing standard
 // argument conversions.
-template <bool is_range> class ShadowFrameGetter {
+template <bool is_range>
+class ShadowFrameGetter {
  public:
   ShadowFrameGetter(size_t first_src_reg,
                     const uint32_t (&arg)[Instruction::kMaxVarArgRegs],
@@ -246,6 +202,17 @@
   size_t arg_index_;
 };
 
+template <bool is_range, bool do_assignability_check>
+bool DoInvokePolymorphic(Thread* self,
+                         ArtMethod* invoke_method,
+                         ShadowFrame& shadow_frame,
+                         Handle<mirror::MethodHandleImpl> method_handle,
+                         Handle<mirror::MethodType> callsite_type,
+                         const uint32_t (&args)[Instruction::kMaxVarArgRegs],
+                         uint32_t first_arg,
+                         JValue* result)
+    REQUIRES_SHARED(Locks::mutator_lock_);
+
 }  // namespace art
 
 #endif  // ART_RUNTIME_METHOD_HANDLES_H_
diff --git a/runtime/mirror/dex_cache.h b/runtime/mirror/dex_cache.h
index ec265e5..6f88cc5 100644
--- a/runtime/mirror/dex_cache.h
+++ b/runtime/mirror/dex_cache.h
@@ -19,7 +19,6 @@
 
 #include "array.h"
 #include "art_field.h"
-#include "art_method.h"
 #include "class.h"
 #include "dex_file_types.h"
 #include "object.h"
@@ -27,6 +26,7 @@
 
 namespace art {
 
+class ArtMethod;
 struct DexCacheOffsets;
 class DexFile;
 class ImageWriter;
diff --git a/runtime/mirror/method_handle_impl.h b/runtime/mirror/method_handle_impl.h
index 5ea82b5..abe999a 100644
--- a/runtime/mirror/method_handle_impl.h
+++ b/runtime/mirror/method_handle_impl.h
@@ -32,6 +32,37 @@
 // C++ mirror of java.lang.invoke.MethodHandle
 class MANAGED MethodHandle : public Object {
  public:
+  // Defines the behaviour of a given method handle. The behaviour
+  // of a handle of a given kind is identical to the dex bytecode behaviour
+  // of the equivalent instruction.
+  //
+  // NOTE: These must be kept in sync with the constants defined in
+  // java.lang.invoke.MethodHandle.
+  enum Kind {
+    kInvokeVirtual = 0,
+    kInvokeSuper,
+    kInvokeDirect,
+    kInvokeStatic,
+    kInvokeInterface,
+    kInvokeTransform,
+    kInvokeCallSiteTransform,
+    kInstanceGet,
+    kInstancePut,
+    kStaticGet,
+    kStaticPut,
+    kLastValidKind = kStaticPut,
+    kFirstAccessorKind = kInstanceGet,
+    kLastAccessorKind = kStaticPut,
+    kLastInvokeKind = kInvokeCallSiteTransform
+  };
+
+  Kind GetHandleKind() REQUIRES_SHARED(Locks::mutator_lock_) {
+    const int32_t handle_kind = GetField32(OFFSET_OF_OBJECT_MEMBER(MethodHandle, handle_kind_));
+    DCHECK(handle_kind >= 0 &&
+           handle_kind <= static_cast<int32_t>(Kind::kLastValidKind));
+    return static_cast<Kind>(handle_kind);
+  }
+
   mirror::MethodType* GetMethodType() REQUIRES_SHARED(Locks::mutator_lock_) {
     return GetFieldObject<mirror::MethodType>(OFFSET_OF_OBJECT_MEMBER(MethodHandle, method_type_));
   }
@@ -50,13 +81,6 @@
         GetField64(OFFSET_OF_OBJECT_MEMBER(MethodHandle, art_field_or_method_)));
   }
 
-  MethodHandleKind GetHandleKind() REQUIRES_SHARED(Locks::mutator_lock_) {
-    const int32_t handle_kind = GetField32(OFFSET_OF_OBJECT_MEMBER(MethodHandle, handle_kind_));
-
-    DCHECK(handle_kind >= 0 && handle_kind <= MethodHandleKind::kLastValidKind);
-    return static_cast<MethodHandleKind>(handle_kind);
-  }
-
   static mirror::Class* StaticClass() REQUIRES_SHARED(Locks::mutator_lock_);
 
  private:
diff --git a/runtime/mirror/object-inl.h b/runtime/mirror/object-inl.h
index 6d29ed3..354410e 100644
--- a/runtime/mirror/object-inl.h
+++ b/runtime/mirror/object-inl.h
@@ -97,6 +97,12 @@
       OFFSET_OF_OBJECT_MEMBER(Object, monitor_), old_val.GetValue(), new_val.GetValue());
 }
 
+inline bool Object::CasLockWordWeakAcquire(LockWord old_val, LockWord new_val) {
+  // Force use of non-transactional mode and do not check.
+  return CasFieldWeakAcquire32<false, false>(
+      OFFSET_OF_OBJECT_MEMBER(Object, monitor_), old_val.GetValue(), new_val.GetValue());
+}
+
 inline bool Object::CasLockWordWeakRelease(LockWord old_val, LockWord new_val) {
   // Force use of non-transactional mode and do not check.
   return CasFieldWeakRelease32<false, false>(
@@ -759,6 +765,24 @@
 }
 
 template<bool kTransactionActive, bool kCheckTransaction, VerifyObjectFlags kVerifyFlags>
+inline bool Object::CasFieldWeakAcquire32(MemberOffset field_offset,
+                                          int32_t old_value, int32_t new_value) {
+  if (kCheckTransaction) {
+    DCHECK_EQ(kTransactionActive, Runtime::Current()->IsActiveTransaction());
+  }
+  if (kTransactionActive) {
+    Runtime::Current()->RecordWriteField32(this, field_offset, old_value, true);
+  }
+  if (kVerifyFlags & kVerifyThis) {
+    VerifyObject(this);
+  }
+  uint8_t* raw_addr = reinterpret_cast<uint8_t*>(this) + field_offset.Int32Value();
+  AtomicInteger* atomic_addr = reinterpret_cast<AtomicInteger*>(raw_addr);
+
+  return atomic_addr->CompareExchangeWeakAcquire(old_value, new_value);
+}
+
+template<bool kTransactionActive, bool kCheckTransaction, VerifyObjectFlags kVerifyFlags>
 inline bool Object::CasFieldWeakRelease32(MemberOffset field_offset,
                                           int32_t old_value, int32_t new_value) {
   if (kCheckTransaction) {
diff --git a/runtime/mirror/object.h b/runtime/mirror/object.h
index 67b5ddb..db58a60 100644
--- a/runtime/mirror/object.h
+++ b/runtime/mirror/object.h
@@ -153,6 +153,8 @@
       REQUIRES_SHARED(Locks::mutator_lock_);
   bool CasLockWordWeakRelaxed(LockWord old_val, LockWord new_val)
       REQUIRES_SHARED(Locks::mutator_lock_);
+  bool CasLockWordWeakAcquire(LockWord old_val, LockWord new_val)
+      REQUIRES_SHARED(Locks::mutator_lock_);
   bool CasLockWordWeakRelease(LockWord old_val, LockWord new_val)
       REQUIRES_SHARED(Locks::mutator_lock_);
   uint32_t GetLockOwnerThreadId();
@@ -460,6 +462,12 @@
 
   template<bool kTransactionActive, bool kCheckTransaction = true,
       VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags>
+  bool CasFieldWeakAcquire32(MemberOffset field_offset, int32_t old_value,
+                             int32_t new_value) ALWAYS_INLINE
+      REQUIRES_SHARED(Locks::mutator_lock_);
+
+  template<bool kTransactionActive, bool kCheckTransaction = true,
+      VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags>
   bool CasFieldWeakRelease32(MemberOffset field_offset, int32_t old_value,
                              int32_t new_value) ALWAYS_INLINE
       REQUIRES_SHARED(Locks::mutator_lock_);
diff --git a/runtime/monitor.cc b/runtime/monitor.cc
index 222eb5c..893abd5 100644
--- a/runtime/monitor.cc
+++ b/runtime/monitor.cc
@@ -161,7 +161,7 @@
   }
   LockWord fat(this, lw.GCState());
   // Publish the updated lock word, which may race with other threads.
-  bool success = GetObject()->CasLockWordWeakSequentiallyConsistent(lw, fat);
+  bool success = GetObject()->CasLockWordWeakRelease(lw, fat);
   // Lock profiling.
   if (success && owner_ != nullptr && lock_profiling_threshold_ != 0) {
     // Do not abort on dex pc errors. This can easily happen when we want to dump a stack trace on
@@ -879,13 +879,16 @@
   StackHandleScope<1> hs(self);
   Handle<mirror::Object> h_obj(hs.NewHandle(obj));
   while (true) {
-    LockWord lock_word = h_obj->GetLockWord(true);
+    // We initially read the lockword with ordinary Java/relaxed semantics. When stronger
+    // semantics are needed, we address it below. Since GetLockWord bottoms out to a relaxed load,
+    // we can fix it later, in an infrequently executed case, with a fence.
+    LockWord lock_word = h_obj->GetLockWord(false);
     switch (lock_word.GetState()) {
       case LockWord::kUnlocked: {
+        // No ordering required for preceding lockword read, since we retest.
         LockWord thin_locked(LockWord::FromThinLockId(thread_id, 0, lock_word.GCState()));
-        if (h_obj->CasLockWordWeakSequentiallyConsistent(lock_word, thin_locked)) {
+        if (h_obj->CasLockWordWeakAcquire(lock_word, thin_locked)) {
           AtraceMonitorLock(self, h_obj.Get(), false /* is_wait */);
-          // CasLockWord enforces more than the acquire ordering we need here.
           return h_obj.Get();  // Success!
         }
         continue;  // Go again.
@@ -893,19 +896,22 @@
       case LockWord::kThinLocked: {
         uint32_t owner_thread_id = lock_word.ThinLockOwner();
         if (owner_thread_id == thread_id) {
+          // No ordering required for initial lockword read.
           // We own the lock, increase the recursion count.
           uint32_t new_count = lock_word.ThinLockCount() + 1;
           if (LIKELY(new_count <= LockWord::kThinLockMaxCount)) {
             LockWord thin_locked(LockWord::FromThinLockId(thread_id,
                                                           new_count,
                                                           lock_word.GCState()));
+            // Only this thread pays attention to the count. Thus there is no need for stronger
+            // than relaxed memory ordering.
             if (!kUseReadBarrier) {
-              h_obj->SetLockWord(thin_locked, true);
+              h_obj->SetLockWord(thin_locked, false /* volatile */);
               AtraceMonitorLock(self, h_obj.Get(), false /* is_wait */);
               return h_obj.Get();  // Success!
             } else {
               // Use CAS to preserve the read barrier state.
-              if (h_obj->CasLockWordWeakSequentiallyConsistent(lock_word, thin_locked)) {
+              if (h_obj->CasLockWordWeakRelaxed(lock_word, thin_locked)) {
                 AtraceMonitorLock(self, h_obj.Get(), false /* is_wait */);
                 return h_obj.Get();  // Success!
               }
@@ -922,20 +928,28 @@
           // Contention.
           contention_count++;
           Runtime* runtime = Runtime::Current();
-          if (contention_count <= runtime->GetMaxSpinsBeforeThinkLockInflation()) {
+          if (contention_count <= runtime->GetMaxSpinsBeforeThinLockInflation()) {
             // TODO: Consider switching the thread state to kBlocked when we are yielding.
             // Use sched_yield instead of NanoSleep since NanoSleep can wait much longer than the
             // parameter you pass in. This can cause thread suspension to take excessively long
             // and make long pauses. See b/16307460.
+            // TODO: We should literally spin first, without sched_yield. Sched_yield either does
+            // nothing (at significant expense), or guarantees that we wait at least microseconds.
+            // If the owner is running, I would expect the median lock hold time to be hundreds
+            // of nanoseconds or less.
             sched_yield();
           } else {
             contention_count = 0;
+            // No ordering required for initial lockword read. Install rereads it anyway.
             InflateThinLocked(self, h_obj, lock_word, 0);
           }
         }
         continue;  // Start from the beginning.
       }
       case LockWord::kFatLocked: {
+        // We should have done an acquire read of the lockword initially, to ensure
+        // visibility of the monitor data structure. Use an explicit fence instead.
+        QuasiAtomic::ThreadFenceAcquire();
         Monitor* mon = lock_word.FatLockMonitor();
         if (trylock) {
           return mon->TryLock(self) ? h_obj.Get() : nullptr;
@@ -946,6 +960,8 @@
       }
       case LockWord::kHashCode:
         // Inflate with the existing hashcode.
+        // Again no ordering required for initial lockword read, since we don't rely
+        // on the visibility of any prior computation.
         Inflate(self, nullptr, h_obj.Get(), lock_word.GetHashCode());
         continue;  // Start from the beginning.
       default: {
@@ -988,13 +1004,16 @@
           }
           if (!kUseReadBarrier) {
             DCHECK_EQ(new_lw.ReadBarrierState(), 0U);
+            // TODO: This really only needs memory_order_release, but we currently have
+            // no way to specify that. In fact there seem to be no legitimate uses of SetLockWord
+            // with a final argument of true. This slows down x86 and ARMv7, but probably not v8.
             h_obj->SetLockWord(new_lw, true);
             AtraceMonitorUnlock();
             // Success!
             return true;
           } else {
             // Use CAS to preserve the read barrier state.
-            if (h_obj->CasLockWordWeakSequentiallyConsistent(lock_word, new_lw)) {
+            if (h_obj->CasLockWordWeakRelease(lock_word, new_lw)) {
               AtraceMonitorUnlock();
               // Success!
               return true;
diff --git a/runtime/openjdkjvmti/Android.bp b/runtime/openjdkjvmti/Android.bp
index 0f9fbb2..d486380 100644
--- a/runtime/openjdkjvmti/Android.bp
+++ b/runtime/openjdkjvmti/Android.bp
@@ -21,8 +21,10 @@
            "object_tagging.cc",
            "OpenjdkJvmTi.cc",
            "ti_class.cc",
+           "ti_field.cc",
            "ti_heap.cc",
            "ti_method.cc",
+           "ti_object.cc",
            "ti_stack.cc",
            "ti_redefine.cc",
            "transform.cc"],
diff --git a/runtime/openjdkjvmti/OpenjdkJvmTi.cc b/runtime/openjdkjvmti/OpenjdkJvmTi.cc
index 5f97b60..c32f8b7 100644
--- a/runtime/openjdkjvmti/OpenjdkJvmTi.cc
+++ b/runtime/openjdkjvmti/OpenjdkJvmTi.cc
@@ -47,8 +47,10 @@
 #include "thread-inl.h"
 #include "thread_list.h"
 #include "ti_class.h"
+#include "ti_field.h"
 #include "ti_heap.h"
 #include "ti_method.h"
+#include "ti_object.h"
 #include "ti_redefine.h"
 #include "ti_stack.h"
 #include "transform.h"
@@ -537,7 +539,7 @@
   }
 
   static jvmtiError GetClassStatus(jvmtiEnv* env, jclass klass, jint* status_ptr) {
-    return ERR(NOT_IMPLEMENTED);
+    return ClassUtil::GetClassStatus(env, klass, status_ptr);
   }
 
   static jvmtiError GetSourceFileName(jvmtiEnv* env, jclass klass, char** source_name_ptr) {
@@ -545,28 +547,28 @@
   }
 
   static jvmtiError GetClassModifiers(jvmtiEnv* env, jclass klass, jint* modifiers_ptr) {
-    return ERR(NOT_IMPLEMENTED);
+    return ClassUtil::GetClassModifiers(env, klass, modifiers_ptr);
   }
 
   static jvmtiError GetClassMethods(jvmtiEnv* env,
                                     jclass klass,
                                     jint* method_count_ptr,
                                     jmethodID** methods_ptr) {
-    return ERR(NOT_IMPLEMENTED);
+    return ClassUtil::GetClassMethods(env, klass, method_count_ptr, methods_ptr);
   }
 
   static jvmtiError GetClassFields(jvmtiEnv* env,
                                    jclass klass,
                                    jint* field_count_ptr,
                                    jfieldID** fields_ptr) {
-    return ERR(NOT_IMPLEMENTED);
+    return ClassUtil::GetClassFields(env, klass, field_count_ptr, fields_ptr);
   }
 
   static jvmtiError GetImplementedInterfaces(jvmtiEnv* env,
                                              jclass klass,
                                              jint* interface_count_ptr,
                                              jclass** interfaces_ptr) {
-    return ERR(NOT_IMPLEMENTED);
+    return ClassUtil::GetImplementedInterfaces(env, klass, interface_count_ptr, interfaces_ptr);
   }
 
   static jvmtiError GetClassVersionNumbers(jvmtiEnv* env,
@@ -585,13 +587,13 @@
   }
 
   static jvmtiError IsInterface(jvmtiEnv* env, jclass klass, jboolean* is_interface_ptr) {
-    return ERR(NOT_IMPLEMENTED);
+    return ClassUtil::IsInterface(env, klass, is_interface_ptr);
   }
 
   static jvmtiError IsArrayClass(jvmtiEnv* env,
                                  jclass klass,
                                  jboolean* is_array_class_ptr) {
-    return ERR(NOT_IMPLEMENTED);
+    return ClassUtil::IsArrayClass(env, klass, is_array_class_ptr);
   }
 
   static jvmtiError IsModifiableClass(jvmtiEnv* env,
@@ -601,7 +603,7 @@
   }
 
   static jvmtiError GetClassLoader(jvmtiEnv* env, jclass klass, jobject* classloader_ptr) {
-    return ERR(NOT_IMPLEMENTED);
+    return ClassUtil::GetClassLoader(env, klass, classloader_ptr);
   }
 
   static jvmtiError GetSourceDebugExtension(jvmtiEnv* env,
@@ -621,11 +623,11 @@
   }
 
   static jvmtiError GetObjectSize(jvmtiEnv* env, jobject object, jlong* size_ptr) {
-    return ERR(NOT_IMPLEMENTED);
+    return ObjectUtil::GetObjectSize(env, object, size_ptr);
   }
 
   static jvmtiError GetObjectHashCode(jvmtiEnv* env, jobject object, jint* hash_code_ptr) {
-    return ERR(NOT_IMPLEMENTED);
+    return ObjectUtil::GetObjectHashCode(env, object, hash_code_ptr);
   }
 
   static jvmtiError GetObjectMonitorUsage(jvmtiEnv* env,
@@ -640,28 +642,28 @@
                                  char** name_ptr,
                                  char** signature_ptr,
                                  char** generic_ptr) {
-    return ERR(NOT_IMPLEMENTED);
+    return FieldUtil::GetFieldName(env, klass, field, name_ptr, signature_ptr, generic_ptr);
   }
 
   static jvmtiError GetFieldDeclaringClass(jvmtiEnv* env,
                                            jclass klass,
                                            jfieldID field,
                                            jclass* declaring_class_ptr) {
-    return ERR(NOT_IMPLEMENTED);
+    return FieldUtil::GetFieldDeclaringClass(env, klass, field, declaring_class_ptr);
   }
 
   static jvmtiError GetFieldModifiers(jvmtiEnv* env,
                                       jclass klass,
                                       jfieldID field,
                                       jint* modifiers_ptr) {
-    return ERR(NOT_IMPLEMENTED);
+    return FieldUtil::GetFieldModifiers(env, klass, field, modifiers_ptr);
   }
 
   static jvmtiError IsFieldSynthetic(jvmtiEnv* env,
                                      jclass klass,
                                      jfieldID field,
                                      jboolean* is_synthetic_ptr) {
-    return ERR(NOT_IMPLEMENTED);
+    return FieldUtil::IsFieldSynthetic(env, klass, field, is_synthetic_ptr);
   }
 
   static jvmtiError GetMethodName(jvmtiEnv* env,
@@ -687,13 +689,13 @@
   static jvmtiError GetMaxLocals(jvmtiEnv* env,
                                  jmethodID method,
                                  jint* max_ptr) {
-    return ERR(NOT_IMPLEMENTED);
+    return MethodUtil::GetMaxLocals(env, method, max_ptr);
   }
 
   static jvmtiError GetArgumentsSize(jvmtiEnv* env,
                                      jmethodID method,
                                      jint* size_ptr) {
-    return ERR(NOT_IMPLEMENTED);
+    return MethodUtil::GetArgumentsSize(env, method, size_ptr);
   }
 
   static jvmtiError GetLineNumberTable(jvmtiEnv* env,
@@ -707,7 +709,7 @@
                                       jmethodID method,
                                       jlocation* start_location_ptr,
                                       jlocation* end_location_ptr) {
-    return ERR(NOT_IMPLEMENTED);
+    return MethodUtil::GetMethodLocation(env, method, start_location_ptr, end_location_ptr);
   }
 
   static jvmtiError GetLocalVariableTable(jvmtiEnv* env,
@@ -838,19 +840,28 @@
   static jvmtiError GetExtensionFunctions(jvmtiEnv* env,
                                           jint* extension_count_ptr,
                                           jvmtiExtensionFunctionInfo** extensions) {
-    return ERR(NOT_IMPLEMENTED);
+    // We do not have any extension functions.
+    *extension_count_ptr = 0;
+    *extensions = nullptr;
+
+    return ERR(NONE);
   }
 
   static jvmtiError GetExtensionEvents(jvmtiEnv* env,
                                        jint* extension_count_ptr,
                                        jvmtiExtensionEventInfo** extensions) {
-    return ERR(NOT_IMPLEMENTED);
+    // We do not have any extension events.
+    *extension_count_ptr = 0;
+    *extensions = nullptr;
+
+    return ERR(NONE);
   }
 
   static jvmtiError SetExtensionEventCallback(jvmtiEnv* env,
                                               jint extension_event_index,
                                               jvmtiExtensionEvent callback) {
-    return ERR(NOT_IMPLEMENTED);
+    // We do not have any extension events, so any call is illegal.
+    return ERR(ILLEGAL_ARGUMENT);
   }
 
   static jvmtiError GetPotentialCapabilities(jvmtiEnv* env, jvmtiCapabilities* capabilities_ptr) {
@@ -1066,8 +1077,15 @@
     ENSURE_NON_NULL(name_ptr);
     switch (error) {
 #define ERROR_CASE(e) case (JVMTI_ERROR_ ## e) : do { \
-          *name_ptr = const_cast<char*>("JVMTI_ERROR_"#e); \
-          return OK; \
+          jvmtiError res = CopyString(env, \
+                                      "JVMTI_ERROR_"#e, \
+                                      reinterpret_cast<unsigned char**>(name_ptr)); \
+          if (res != OK) { \
+            *name_ptr = nullptr; \
+            return res; \
+          } else { \
+            return OK; \
+          } \
         } while (false)
       ERROR_CASE(NONE);
       ERROR_CASE(INVALID_THREAD);
@@ -1119,8 +1137,15 @@
       ERROR_CASE(INVALID_ENVIRONMENT);
 #undef ERROR_CASE
       default: {
-        *name_ptr = const_cast<char*>("JVMTI_ERROR_UNKNOWN");
-        return ERR(ILLEGAL_ARGUMENT);
+        jvmtiError res = CopyString(env,
+                                    "JVMTI_ERROR_UNKNOWN",
+                                    reinterpret_cast<unsigned char**>(name_ptr));
+        if (res != OK) {
+          *name_ptr = nullptr;
+          return res;
+        } else {
+          return ERR(ILLEGAL_ARGUMENT);
+        }
       }
     }
   }
diff --git a/runtime/openjdkjvmti/ti_class.cc b/runtime/openjdkjvmti/ti_class.cc
index de2076a..0d1704c 100644
--- a/runtime/openjdkjvmti/ti_class.cc
+++ b/runtime/openjdkjvmti/ti_class.cc
@@ -32,11 +32,146 @@
 #include "ti_class.h"
 
 #include "art_jvmti.h"
+#include "jni_internal.h"
 #include "scoped_thread_state_change-inl.h"
 #include "thread-inl.h"
 
 namespace openjdkjvmti {
 
+jvmtiError ClassUtil::GetClassFields(jvmtiEnv* env,
+                                     jclass jklass,
+                                     jint* field_count_ptr,
+                                     jfieldID** fields_ptr) {
+  art::ScopedObjectAccess soa(art::Thread::Current());
+  art::ObjPtr<art::mirror::Class> klass = soa.Decode<art::mirror::Class>(jklass);
+  if (klass == nullptr) {
+    return ERR(INVALID_CLASS);
+  }
+
+  if (field_count_ptr == nullptr || fields_ptr == nullptr) {
+    return ERR(NULL_POINTER);
+  }
+
+  art::IterationRange<art::StrideIterator<art::ArtField>> ifields = klass->GetIFields();
+  art::IterationRange<art::StrideIterator<art::ArtField>> sfields = klass->GetSFields();
+  size_t array_size = klass->NumInstanceFields() + klass->NumStaticFields();
+
+  unsigned char* out_ptr;
+  jvmtiError allocError = env->Allocate(array_size * sizeof(jfieldID), &out_ptr);
+  if (allocError != ERR(NONE)) {
+    return allocError;
+  }
+  jfieldID* field_array = reinterpret_cast<jfieldID*>(out_ptr);
+
+  size_t array_idx = 0;
+  for (art::ArtField& field : sfields) {
+    field_array[array_idx] = art::jni::EncodeArtField(&field);
+    ++array_idx;
+  }
+  for (art::ArtField& field : ifields) {
+    field_array[array_idx] = art::jni::EncodeArtField(&field);
+    ++array_idx;
+  }
+
+  *field_count_ptr = static_cast<jint>(array_size);
+  *fields_ptr = field_array;
+
+  return ERR(NONE);
+}
+
+jvmtiError ClassUtil::GetClassMethods(jvmtiEnv* env,
+                                      jclass jklass,
+                                      jint* method_count_ptr,
+                                      jmethodID** methods_ptr) {
+  art::ScopedObjectAccess soa(art::Thread::Current());
+  art::ObjPtr<art::mirror::Class> klass = soa.Decode<art::mirror::Class>(jklass);
+  if (klass == nullptr) {
+    return ERR(INVALID_CLASS);
+  }
+
+  if (method_count_ptr == nullptr || methods_ptr == nullptr) {
+    return ERR(NULL_POINTER);
+  }
+
+  size_t array_size = klass->NumDeclaredVirtualMethods() + klass->NumDirectMethods();
+  unsigned char* out_ptr;
+  jvmtiError allocError = env->Allocate(array_size * sizeof(jmethodID), &out_ptr);
+  if (allocError != ERR(NONE)) {
+    return allocError;
+  }
+  jmethodID* method_array = reinterpret_cast<jmethodID*>(out_ptr);
+
+  if (art::kIsDebugBuild) {
+    size_t count = 0;
+    for (auto& m ATTRIBUTE_UNUSED : klass->GetDeclaredMethods(art::kRuntimePointerSize)) {
+      count++;
+    }
+    CHECK_EQ(count, klass->NumDirectMethods() + klass->NumDeclaredVirtualMethods());
+  }
+
+  size_t array_idx = 0;
+  for (auto& m : klass->GetDeclaredMethods(art::kRuntimePointerSize)) {
+    method_array[array_idx] = art::jni::EncodeArtMethod(&m);
+    ++array_idx;
+  }
+
+  *method_count_ptr = static_cast<jint>(array_size);
+  *methods_ptr = method_array;
+
+  return ERR(NONE);
+}
+
+jvmtiError ClassUtil::GetImplementedInterfaces(jvmtiEnv* env,
+                                               jclass jklass,
+                                               jint* interface_count_ptr,
+                                               jclass** interfaces_ptr) {
+  art::ScopedObjectAccess soa(art::Thread::Current());
+  art::ObjPtr<art::mirror::Class> klass = soa.Decode<art::mirror::Class>(jklass);
+  if (klass == nullptr) {
+    return ERR(INVALID_CLASS);
+  }
+
+  if (interface_count_ptr == nullptr || interfaces_ptr == nullptr) {
+    return ERR(NULL_POINTER);
+  }
+
+  // Need to handle array specifically. Arrays implement Serializable and Cloneable, but the
+  // spec says these should not be reported.
+  if (klass->IsArrayClass()) {
+    *interface_count_ptr = 0;
+    *interfaces_ptr = nullptr;  // TODO: Should we allocate a dummy here?
+    return ERR(NONE);
+  }
+
+  size_t array_size = klass->NumDirectInterfaces();
+  unsigned char* out_ptr;
+  jvmtiError allocError = env->Allocate(array_size * sizeof(jclass), &out_ptr);
+  if (allocError != ERR(NONE)) {
+    return allocError;
+  }
+  jclass* interface_array = reinterpret_cast<jclass*>(out_ptr);
+
+  art::StackHandleScope<1> hs(soa.Self());
+  art::Handle<art::mirror::Class> h_klass(hs.NewHandle(klass));
+
+  for (uint32_t idx = 0; idx != array_size; ++idx) {
+    art::ObjPtr<art::mirror::Class> inf_klass =
+        art::mirror::Class::ResolveDirectInterface(soa.Self(), h_klass, idx);
+    if (inf_klass == nullptr) {
+      soa.Self()->ClearException();
+      env->Deallocate(out_ptr);
+      // TODO: What is the right error code here?
+      return ERR(INTERNAL);
+    }
+    interface_array[idx] = soa.AddLocalReference<jclass>(inf_klass);
+  }
+
+  *interface_count_ptr = static_cast<jint>(array_size);
+  *interfaces_ptr = interface_array;
+
+  return ERR(NONE);
+}
+
 jvmtiError ClassUtil::GetClassSignature(jvmtiEnv* env,
                                          jclass jklass,
                                          char** signature_ptr,
@@ -70,4 +205,127 @@
   return ERR(NONE);
 }
 
+jvmtiError ClassUtil::GetClassStatus(jvmtiEnv* env ATTRIBUTE_UNUSED,
+                                     jclass jklass,
+                                     jint* status_ptr) {
+  art::ScopedObjectAccess soa(art::Thread::Current());
+  art::ObjPtr<art::mirror::Class> klass = soa.Decode<art::mirror::Class>(jklass);
+  if (klass == nullptr) {
+    return ERR(INVALID_CLASS);
+  }
+
+  if (status_ptr == nullptr) {
+    return ERR(NULL_POINTER);
+  }
+
+  if (klass->IsArrayClass()) {
+    *status_ptr = JVMTI_CLASS_STATUS_ARRAY;
+  } else if (klass->IsPrimitive()) {
+    *status_ptr = JVMTI_CLASS_STATUS_PRIMITIVE;
+  } else {
+    *status_ptr = JVMTI_CLASS_STATUS_VERIFIED;  // All loaded classes are structurally verified.
+    // This is finicky. If there's an error, we'll say it wasn't prepared.
+    if (klass->IsResolved()) {
+      *status_ptr |= JVMTI_CLASS_STATUS_PREPARED;
+    }
+    if (klass->IsInitialized()) {
+      *status_ptr |= JVMTI_CLASS_STATUS_INITIALIZED;
+    }
+    // Technically the class may be erroneous for other reasons, but we do not have enough info.
+    if (klass->IsErroneous()) {
+      *status_ptr |= JVMTI_CLASS_STATUS_ERROR;
+    }
+  }
+
+  return ERR(NONE);
+}
+
+template <typename T>
+static jvmtiError ClassIsT(jclass jklass, T test, jboolean* is_t_ptr) {
+  art::ScopedObjectAccess soa(art::Thread::Current());
+  art::ObjPtr<art::mirror::Class> klass = soa.Decode<art::mirror::Class>(jklass);
+  if (klass == nullptr) {
+    return ERR(INVALID_CLASS);
+  }
+
+  if (is_t_ptr == nullptr) {
+    return ERR(NULL_POINTER);
+  }
+
+  *is_t_ptr = test(klass) ? JNI_TRUE : JNI_FALSE;
+  return ERR(NONE);
+}
+
+jvmtiError ClassUtil::IsInterface(jvmtiEnv* env ATTRIBUTE_UNUSED,
+                                  jclass jklass,
+                                  jboolean* is_interface_ptr) {
+  auto test = [](art::ObjPtr<art::mirror::Class> klass) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    return klass->IsInterface();
+  };
+  return ClassIsT(jklass, test, is_interface_ptr);
+}
+
+jvmtiError ClassUtil::IsArrayClass(jvmtiEnv* env ATTRIBUTE_UNUSED,
+                                   jclass jklass,
+                                   jboolean* is_array_class_ptr) {
+  auto test = [](art::ObjPtr<art::mirror::Class> klass) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    return klass->IsArrayClass();
+  };
+  return ClassIsT(jklass, test, is_array_class_ptr);
+}
+
+// Keep this in sync with Class.getModifiers().
+static uint32_t ClassGetModifiers(art::Thread* self, art::ObjPtr<art::mirror::Class> klass)
+    REQUIRES_SHARED(art::Locks::mutator_lock_) {
+  if (klass->IsArrayClass()) {
+    uint32_t component_modifiers = ClassGetModifiers(self, klass->GetComponentType());
+    if ((component_modifiers & art::kAccInterface) != 0) {
+      component_modifiers &= ~(art::kAccInterface | art::kAccStatic);
+    }
+    return art::kAccAbstract | art::kAccFinal | component_modifiers;
+  }
+
+  uint32_t modifiers = klass->GetAccessFlags() & art::kAccJavaFlagsMask;
+
+  art::StackHandleScope<1> hs(self);
+  art::Handle<art::mirror::Class> h_klass(hs.NewHandle(klass));
+  return art::mirror::Class::GetInnerClassFlags(h_klass, modifiers);
+}
+
+jvmtiError ClassUtil::GetClassModifiers(jvmtiEnv* env ATTRIBUTE_UNUSED,
+                                        jclass jklass,
+                                        jint* modifiers_ptr) {
+  art::ScopedObjectAccess soa(art::Thread::Current());
+  art::ObjPtr<art::mirror::Class> klass = soa.Decode<art::mirror::Class>(jklass);
+  if (klass == nullptr) {
+    return ERR(INVALID_CLASS);
+  }
+
+  if (modifiers_ptr == nullptr) {
+    return ERR(NULL_POINTER);
+  }
+
+  *modifiers_ptr = ClassGetModifiers(soa.Self(), klass);
+
+  return ERR(NONE);
+}
+
+jvmtiError ClassUtil::GetClassLoader(jvmtiEnv* env ATTRIBUTE_UNUSED,
+                                     jclass jklass,
+                                     jobject* classloader_ptr) {
+  art::ScopedObjectAccess soa(art::Thread::Current());
+  art::ObjPtr<art::mirror::Class> klass = soa.Decode<art::mirror::Class>(jklass);
+  if (klass == nullptr) {
+    return ERR(INVALID_CLASS);
+  }
+
+  if (classloader_ptr == nullptr) {
+    return ERR(NULL_POINTER);
+  }
+
+  *classloader_ptr = soa.AddLocalReference<jobject>(klass->GetClassLoader());
+
+  return ERR(NONE);
+}
+
 }  // namespace openjdkjvmti
diff --git a/runtime/openjdkjvmti/ti_class.h b/runtime/openjdkjvmti/ti_class.h
index caa77d4..577fc8e 100644
--- a/runtime/openjdkjvmti/ti_class.h
+++ b/runtime/openjdkjvmti/ti_class.h
@@ -39,10 +39,34 @@
 
 class ClassUtil {
  public:
+  static jvmtiError GetClassFields(jvmtiEnv* env,
+                                   jclass klass,
+                                   jint* field_count_ptr,
+                                   jfieldID** fields_ptr);
+
+  static jvmtiError GetClassMethods(jvmtiEnv* env,
+                                    jclass klass,
+                                    jint* method_count_ptr,
+                                    jmethodID** methods_ptr);
+
+  static jvmtiError GetImplementedInterfaces(jvmtiEnv* env,
+                                             jclass klass,
+                                             jint* interface_count_ptr,
+                                             jclass** interfaces_ptr);
+
+  static jvmtiError GetClassModifiers(jvmtiEnv* env, jclass klass, jint* modifiers_ptr);
+
   static jvmtiError GetClassSignature(jvmtiEnv* env,
                                       jclass klass,
                                       char** signature_ptr,
                                       char** generic_ptr);
+
+  static jvmtiError GetClassStatus(jvmtiEnv* env, jclass klass, jint* status_ptr);
+
+  static jvmtiError GetClassLoader(jvmtiEnv* env, jclass klass, jobject* classloader_ptr);
+
+  static jvmtiError IsInterface(jvmtiEnv* env, jclass klass, jboolean* is_interface_ptr);
+  static jvmtiError IsArrayClass(jvmtiEnv* env, jclass klass, jboolean* is_array_class_ptr);
 };
 
 }  // namespace openjdkjvmti
diff --git a/runtime/openjdkjvmti/ti_field.cc b/runtime/openjdkjvmti/ti_field.cc
new file mode 100644
index 0000000..a762830
--- /dev/null
+++ b/runtime/openjdkjvmti/ti_field.cc
@@ -0,0 +1,171 @@
+/* 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_field.h"
+
+#include "art_jvmti.h"
+#include "art_field-inl.h"
+#include "base/enums.h"
+#include "jni_internal.h"
+#include "modifiers.h"
+#include "scoped_thread_state_change-inl.h"
+#include "thread-inl.h"
+
+namespace openjdkjvmti {
+
+// Note: For all these functions, we could do a check that the field actually belongs to the given
+//       class. But the spec seems to assume a certain encoding of the field ID, and so doesn't
+//       specify any errors.
+
+jvmtiError FieldUtil::GetFieldName(jvmtiEnv* env,
+                                   jclass klass,
+                                   jfieldID field,
+                                   char** name_ptr,
+                                   char** signature_ptr,
+                                   char** generic_ptr) {
+  if (klass == nullptr) {
+    return ERR(INVALID_CLASS);
+  }
+  if (field == nullptr) {
+    return ERR(INVALID_FIELDID);
+  }
+
+  art::ScopedObjectAccess soa(art::Thread::Current());
+  art::ArtField* art_field = art::jni::DecodeArtField(field);
+
+  JvmtiUniquePtr name_copy;
+  if (name_ptr != nullptr) {
+    const char* field_name = art_field->GetName();
+    if (field_name == nullptr) {
+      field_name = "<error>";
+    }
+    unsigned char* tmp;
+    jvmtiError ret = CopyString(env, field_name, &tmp);
+    if (ret != ERR(NONE)) {
+      return ret;
+    }
+    name_copy = MakeJvmtiUniquePtr(env, tmp);
+    *name_ptr = reinterpret_cast<char*>(tmp);
+  }
+
+  JvmtiUniquePtr signature_copy;
+  if (signature_ptr != nullptr) {
+    const char* sig = art_field->GetTypeDescriptor();
+    unsigned char* tmp;
+    jvmtiError ret = CopyString(env, sig, &tmp);
+    if (ret != ERR(NONE)) {
+      return ret;
+    }
+    signature_copy = MakeJvmtiUniquePtr(env, tmp);
+    *signature_ptr = reinterpret_cast<char*>(tmp);
+  }
+
+  // TODO: Support generic signature.
+  if (generic_ptr != nullptr) {
+    *generic_ptr = nullptr;
+  }
+
+  // Everything is fine, release the buffers.
+  name_copy.release();
+  signature_copy.release();
+
+  return ERR(NONE);
+}
+
+jvmtiError FieldUtil::GetFieldDeclaringClass(jvmtiEnv* env ATTRIBUTE_UNUSED,
+                                             jclass klass,
+                                             jfieldID field,
+                                             jclass* declaring_class_ptr) {
+  if (klass == nullptr) {
+    return ERR(INVALID_CLASS);
+  }
+  if (field == nullptr) {
+    return ERR(INVALID_FIELDID);
+  }
+  if (declaring_class_ptr == nullptr) {
+    return ERR(NULL_POINTER);
+  }
+
+  art::ScopedObjectAccess soa(art::Thread::Current());
+  art::ArtField* art_field = art::jni::DecodeArtField(field);
+  art::ObjPtr<art::mirror::Class> field_klass = art_field->GetDeclaringClass();
+
+  *declaring_class_ptr = soa.AddLocalReference<jclass>(field_klass);
+
+  return ERR(NONE);
+}
+
+jvmtiError FieldUtil::GetFieldModifiers(jvmtiEnv* env ATTRIBUTE_UNUSED,
+                                        jclass klass,
+                                        jfieldID field,
+                                        jint* modifiers_ptr) {
+  if (klass == nullptr) {
+    return ERR(INVALID_CLASS);
+  }
+  if (field == nullptr) {
+    return ERR(INVALID_FIELDID);
+  }
+  if (modifiers_ptr == nullptr) {
+    return ERR(NULL_POINTER);
+  }
+
+  art::ScopedObjectAccess soa(art::Thread::Current());
+  art::ArtField* art_field = art::jni::DecodeArtField(field);
+  // Note: Keep this code in sync with Field.getModifiers.
+  uint32_t modifiers = art_field->GetAccessFlags() & 0xFFFF;
+
+  *modifiers_ptr = modifiers;
+  return ERR(NONE);
+}
+
+jvmtiError FieldUtil::IsFieldSynthetic(jvmtiEnv* env ATTRIBUTE_UNUSED,
+                                       jclass klass,
+                                       jfieldID field,
+                                       jboolean* is_synthetic_ptr) {
+  if (klass == nullptr) {
+    return ERR(INVALID_CLASS);
+  }
+  if (field == nullptr) {
+    return ERR(INVALID_FIELDID);
+  }
+  if (is_synthetic_ptr == nullptr) {
+    return ERR(NULL_POINTER);
+  }
+
+  art::ScopedObjectAccess soa(art::Thread::Current());
+  art::ArtField* art_field = art::jni::DecodeArtField(field);
+  uint32_t modifiers = art_field->GetAccessFlags();
+
+  *is_synthetic_ptr = ((modifiers & art::kAccSynthetic) != 0) ? JNI_TRUE : JNI_FALSE;
+  return ERR(NONE);
+}
+
+}  // namespace openjdkjvmti
diff --git a/runtime/openjdkjvmti/ti_field.h b/runtime/openjdkjvmti/ti_field.h
new file mode 100644
index 0000000..9a29f81
--- /dev/null
+++ b/runtime/openjdkjvmti/ti_field.h
@@ -0,0 +1,67 @@
+/* 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.
+ */
+
+#ifndef ART_RUNTIME_OPENJDKJVMTI_TI_FIELD_H_
+#define ART_RUNTIME_OPENJDKJVMTI_TI_FIELD_H_
+
+#include "jni.h"
+#include "jvmti.h"
+
+namespace openjdkjvmti {
+
+class FieldUtil {
+ public:
+  static jvmtiError GetFieldName(jvmtiEnv* env,
+                                 jclass klass,
+                                 jfieldID field,
+                                 char** name_ptr,
+                                 char** signature_ptr,
+                                 char** generic_ptr);
+
+  static jvmtiError GetFieldDeclaringClass(jvmtiEnv* env,
+                                           jclass klass,
+                                           jfieldID field,
+                                           jclass* declaring_class_ptr);
+
+  static jvmtiError GetFieldModifiers(jvmtiEnv* env,
+                                      jclass klass,
+                                      jfieldID field,
+                                      jint* modifiers_ptr);
+
+  static jvmtiError IsFieldSynthetic(jvmtiEnv* env,
+                                     jclass klass,
+                                     jfieldID field,
+                                     jboolean* is_synthetic_ptr);
+};
+
+}  // namespace openjdkjvmti
+
+#endif  // ART_RUNTIME_OPENJDKJVMTI_TI_FIELD_H_
diff --git a/runtime/openjdkjvmti/ti_method.cc b/runtime/openjdkjvmti/ti_method.cc
index a0a0923..bb48a2b 100644
--- a/runtime/openjdkjvmti/ti_method.cc
+++ b/runtime/openjdkjvmti/ti_method.cc
@@ -37,9 +37,68 @@
 #include "jni_internal.h"
 #include "modifiers.h"
 #include "scoped_thread_state_change-inl.h"
+#include "thread-inl.h"
 
 namespace openjdkjvmti {
 
+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()) {
+    // This isn't specified as an error case, so return 0.
+    *size_ptr = 0;
+    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,
@@ -106,6 +165,38 @@
   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 0/0.
+    *start_location_ptr = 0;
+    *end_location_ptr = 0;
+    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) {
diff --git a/runtime/openjdkjvmti/ti_method.h b/runtime/openjdkjvmti/ti_method.h
index fb2fbb2..ed60620 100644
--- a/runtime/openjdkjvmti/ti_method.h
+++ b/runtime/openjdkjvmti/ti_method.h
@@ -39,6 +39,10 @@
 
 class MethodUtil {
  public:
+  static jvmtiError GetArgumentsSize(jvmtiEnv* env, jmethodID method, jint* size_ptr);
+
+  static jvmtiError GetMaxLocals(jvmtiEnv* env, jmethodID method, jint* max_ptr);
+
   static jvmtiError GetMethodName(jvmtiEnv* env,
                                   jmethodID method,
                                   char** name_ptr,
@@ -49,6 +53,11 @@
                                             jmethodID method,
                                             jclass* declaring_class_ptr);
 
+  static jvmtiError GetMethodLocation(jvmtiEnv* env,
+                                      jmethodID method,
+                                      jlocation* start_location_ptr,
+                                      jlocation* end_location_ptr);
+
   static jvmtiError GetMethodModifiers(jvmtiEnv* env,
                                        jmethodID method,
                                        jint* modifiers_ptr);
diff --git a/runtime/openjdkjvmti/ti_object.cc b/runtime/openjdkjvmti/ti_object.cc
new file mode 100644
index 0000000..bf84499
--- /dev/null
+++ b/runtime/openjdkjvmti/ti_object.cc
@@ -0,0 +1,76 @@
+/* Copyright (C) 2017 The Android Open Source Project
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This file implements interfaces from the file jvmti.h. This implementation
+ * is licensed under the same terms as the file jvmti.h.  The
+ * copyright and license information for the file jvmti.h follows.
+ *
+ * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include "ti_object.h"
+
+#include "art_jvmti.h"
+#include "mirror/object-inl.h"
+#include "scoped_thread_state_change-inl.h"
+#include "thread-inl.h"
+
+namespace openjdkjvmti {
+
+jvmtiError ObjectUtil::GetObjectSize(jvmtiEnv* env ATTRIBUTE_UNUSED,
+                                     jobject jobject,
+                                     jlong* size_ptr) {
+  if (jobject == nullptr) {
+    return ERR(INVALID_OBJECT);
+  }
+  if (size_ptr == nullptr) {
+    return ERR(NULL_POINTER);
+  }
+
+  art::ScopedObjectAccess soa(art::Thread::Current());
+  art::ObjPtr<art::mirror::Object> object = soa.Decode<art::mirror::Object>(jobject);
+
+  *size_ptr = object->SizeOf();
+  return ERR(NONE);
+}
+
+jvmtiError ObjectUtil::GetObjectHashCode(jvmtiEnv* env ATTRIBUTE_UNUSED,
+                                         jobject jobject,
+                                         jint* hash_code_ptr) {
+  if (jobject == nullptr) {
+    return ERR(INVALID_OBJECT);
+  }
+  if (hash_code_ptr == nullptr) {
+    return ERR(NULL_POINTER);
+  }
+
+  art::ScopedObjectAccess soa(art::Thread::Current());
+  art::ObjPtr<art::mirror::Object> object = soa.Decode<art::mirror::Object>(jobject);
+
+  *hash_code_ptr = object->IdentityHashCode();
+
+  return ERR(NONE);
+}
+
+}  // namespace openjdkjvmti
diff --git a/runtime/openjdkjvmti/ti_object.h b/runtime/openjdkjvmti/ti_object.h
new file mode 100644
index 0000000..09eee61
--- /dev/null
+++ b/runtime/openjdkjvmti/ti_object.h
@@ -0,0 +1,49 @@
+/* Copyright (C) 2017 The Android Open Source Project
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This file implements interfaces from the file jvmti.h. This implementation
+ * is licensed under the same terms as the file jvmti.h.  The
+ * copyright and license information for the file jvmti.h follows.
+ *
+ * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef ART_RUNTIME_OPENJDKJVMTI_TI_OBJECT_H_
+#define ART_RUNTIME_OPENJDKJVMTI_TI_OBJECT_H_
+
+#include "jni.h"
+#include "jvmti.h"
+
+namespace openjdkjvmti {
+
+class ObjectUtil {
+ public:
+  static jvmtiError GetObjectSize(jvmtiEnv* env, jobject object, jlong* size_ptr);
+
+  static jvmtiError GetObjectHashCode(jvmtiEnv* env, jobject object, jint* hash_code_ptr);
+};
+
+}  // namespace openjdkjvmti
+
+#endif  // ART_RUNTIME_OPENJDKJVMTI_TI_OBJECT_H_
diff --git a/runtime/openjdkjvmti/ti_redefine.cc b/runtime/openjdkjvmti/ti_redefine.cc
index 68815e7..926819d 100644
--- a/runtime/openjdkjvmti/ti_redefine.cc
+++ b/runtime/openjdkjvmti/ti_redefine.cc
@@ -39,7 +39,10 @@
 #include "base/logging.h"
 #include "events-inl.h"
 #include "gc/allocation_listener.h"
+#include "gc/heap.h"
 #include "instrumentation.h"
+#include "jit/jit.h"
+#include "jit/jit_code_cache.h"
 #include "jni_env_ext-inl.h"
 #include "jvmti_allocator.h"
 #include "mirror/class.h"
@@ -53,6 +56,143 @@
 
 using android::base::StringPrintf;
 
+// This visitor walks thread stacks and allocates and sets up the obsolete methods. It also does
+// some basic sanity checks that the obsolete method is sane.
+class ObsoleteMethodStackVisitor : public art::StackVisitor {
+ protected:
+  ObsoleteMethodStackVisitor(
+      art::Thread* thread,
+      art::LinearAlloc* allocator,
+      const std::unordered_set<art::ArtMethod*>& obsoleted_methods,
+      /*out*/std::unordered_map<art::ArtMethod*, art::ArtMethod*>* obsolete_maps,
+      /*out*/bool* success,
+      /*out*/std::string* error_msg)
+        : StackVisitor(thread,
+                       /*context*/nullptr,
+                       StackVisitor::StackWalkKind::kIncludeInlinedFrames),
+          allocator_(allocator),
+          obsoleted_methods_(obsoleted_methods),
+          obsolete_maps_(obsolete_maps),
+          success_(success),
+          is_runtime_frame_(false),
+          error_msg_(error_msg) {
+    *success_ = true;
+  }
+
+  ~ObsoleteMethodStackVisitor() OVERRIDE {}
+
+ public:
+  // Returns true if we successfully installed obsolete methods on this thread, filling
+  // obsolete_maps_ with the translations if needed. Returns false and fills error_msg if we fail.
+  // The stack is cleaned up when we fail.
+  static bool UpdateObsoleteFrames(
+      art::Thread* thread,
+      art::LinearAlloc* allocator,
+      const std::unordered_set<art::ArtMethod*>& obsoleted_methods,
+      /*out*/std::unordered_map<art::ArtMethod*, art::ArtMethod*>* obsolete_maps,
+      /*out*/std::string* error_msg) REQUIRES(art::Locks::mutator_lock_) {
+    bool success = true;
+    ObsoleteMethodStackVisitor visitor(thread,
+                                       allocator,
+                                       obsoleted_methods,
+                                       obsolete_maps,
+                                       &success,
+                                       error_msg);
+    visitor.WalkStack();
+    if (!success) {
+      RestoreFrames(thread, *obsolete_maps, error_msg);
+      return false;
+    } else {
+      return true;
+    }
+  }
+
+  static void RestoreFrames(
+      art::Thread* thread ATTRIBUTE_UNUSED,
+      const std::unordered_map<art::ArtMethod*, art::ArtMethod*>& obsolete_maps ATTRIBUTE_UNUSED,
+      std::string* error_msg)
+        REQUIRES(art::Locks::mutator_lock_) {
+    LOG(FATAL) << "Restoring stack frames is not yet supported. Error was: " << *error_msg;
+  }
+
+  bool VisitFrame() OVERRIDE REQUIRES(art::Locks::mutator_lock_) {
+    art::ArtMethod* old_method = GetMethod();
+    // TODO REMOVE once either current_method doesn't stick around through suspend points or deopt
+    // works through runtime methods.
+    bool prev_was_runtime_frame_ = is_runtime_frame_;
+    is_runtime_frame_ = old_method->IsRuntimeMethod();
+    if (obsoleted_methods_.find(old_method) != obsoleted_methods_.end()) {
+      // The check below works since when we deoptimize we set shadow frames for all frames until a
+      // native/runtime transition and for those set the return PC to a function that will complete
+      // the deoptimization. This does leave us with the unfortunate side-effect that frames just
+      // below runtime frames cannot be deoptimized at the moment.
+      // TODO REMOVE once either current_method doesn't stick around through suspend points or deopt
+      // works through runtime methods.
+      // TODO b/33616143
+      if (!IsShadowFrame() && prev_was_runtime_frame_) {
+        *error_msg_ = StringPrintf("Deoptimization failed due to runtime method in stack.");
+        *success_ = false;
+        return false;
+      }
+      // We cannot ensure that the right dex file is used in inlined frames so we don't support
+      // redefining them.
+      DCHECK(!IsInInlinedFrame()) << "Inlined frames are not supported when using redefinition";
+      // TODO We should really support intrinsic obsolete methods.
+      // TODO We should really support redefining intrinsics.
+      // We don't support intrinsics so check for them here.
+      DCHECK(!old_method->IsIntrinsic());
+      art::ArtMethod* new_obsolete_method = nullptr;
+      auto obsolete_method_pair = obsolete_maps_->find(old_method);
+      if (obsolete_method_pair == obsolete_maps_->end()) {
+        // Create a new Obsolete Method and put it in the list.
+        art::Runtime* runtime = art::Runtime::Current();
+        art::ClassLinker* cl = runtime->GetClassLinker();
+        auto ptr_size = cl->GetImagePointerSize();
+        const size_t method_size = art::ArtMethod::Size(ptr_size);
+        auto* method_storage = allocator_->Alloc(GetThread(), method_size);
+        if (method_storage == nullptr) {
+          *success_ = false;
+          *error_msg_ = StringPrintf("Unable to allocate storage for obsolete version of '%s'",
+                                     old_method->PrettyMethod().c_str());
+          return false;
+        }
+        new_obsolete_method = new (method_storage) art::ArtMethod();
+        new_obsolete_method->CopyFrom(old_method, ptr_size);
+        DCHECK_EQ(new_obsolete_method->GetDeclaringClass(), old_method->GetDeclaringClass());
+        new_obsolete_method->SetIsObsolete();
+        obsolete_maps_->insert({old_method, new_obsolete_method});
+        // Update JIT Data structures to point to the new method.
+        art::jit::Jit* jit = art::Runtime::Current()->GetJit();
+        if (jit != nullptr) {
+          // Notify the JIT we are making this obsolete method. It will update the jit's internal
+          // structures to keep track of the new obsolete method.
+          jit->GetCodeCache()->MoveObsoleteMethod(old_method, new_obsolete_method);
+        }
+      } else {
+        new_obsolete_method = obsolete_method_pair->second;
+      }
+      DCHECK(new_obsolete_method != nullptr);
+      SetMethod(new_obsolete_method);
+    }
+    return true;
+  }
+
+ private:
+  // The linear allocator we should use to make new methods.
+  art::LinearAlloc* allocator_;
+  // The set of all methods which could be obsoleted.
+  const std::unordered_set<art::ArtMethod*>& obsoleted_methods_;
+  // A map from the original to the newly allocated obsolete method for frames on this thread. The
+  // values in this map must be added to the obsolete_methods_ (and obsolete_dex_caches_) fields of
+  // the redefined classes ClassExt by the caller.
+  std::unordered_map<art::ArtMethod*, art::ArtMethod*>* obsolete_maps_;
+  bool* success_;
+  // TODO REMOVE once either current_method doesn't stick around through suspend points or deopt
+  // works through runtime methods.
+  bool is_runtime_frame_;
+  std::string* error_msg_;
+};
+
 // Moves dex data to an anonymous, read-only mmap'd region.
 std::unique_ptr<art::MemMap> Redefiner::MoveDataToMemMap(const std::string& original_location,
                                                          jint data_len,
@@ -76,6 +216,8 @@
   return map;
 }
 
+// TODO This should handle doing multiple classes at once so we need to do less cleanup when things
+// go wrong.
 jvmtiError Redefiner::RedefineClass(ArtJvmTiEnv* env,
                                     art::Runtime* runtime,
                                     art::Thread* self,
@@ -116,6 +258,9 @@
     *error_msg = os.str();
     return ERR(INVALID_CLASS_FORMAT);
   }
+  // Stop JIT for the duration of this redefine since the JIT might concurrently compile a method we
+  // are going to redefine.
+  art::jit::ScopedJitSuspend suspend_jit;
   // Get shared mutator lock.
   art::ScopedObjectAccess soa(self);
   art::StackHandleScope<1> hs(self);
@@ -296,6 +441,107 @@
   return true;
 }
 
+struct CallbackCtx {
+  Redefiner* const r;
+  art::LinearAlloc* allocator;
+  std::unordered_map<art::ArtMethod*, art::ArtMethod*> obsolete_map;
+  std::unordered_set<art::ArtMethod*> obsolete_methods;
+  bool success;
+  std::string* error_msg;
+
+  CallbackCtx(Redefiner* self, art::LinearAlloc* alloc, std::string* error)
+      : r(self), allocator(alloc), success(true), error_msg(error) {}
+};
+
+void DoRestoreObsoleteMethodsCallback(art::Thread* t, void* vdata) NO_THREAD_SAFETY_ANALYSIS {
+  CallbackCtx* data = reinterpret_cast<CallbackCtx*>(vdata);
+  ObsoleteMethodStackVisitor::RestoreFrames(t, data->obsolete_map, data->error_msg);
+}
+
+void DoAllocateObsoleteMethodsCallback(art::Thread* t, void* vdata) NO_THREAD_SAFETY_ANALYSIS {
+  CallbackCtx* data = reinterpret_cast<CallbackCtx*>(vdata);
+  if (data->success) {
+    // Don't do anything if we already failed once.
+    data->success = ObsoleteMethodStackVisitor::UpdateObsoleteFrames(t,
+                                                                     data->allocator,
+                                                                     data->obsolete_methods,
+                                                                     &data->obsolete_map,
+                                                                     data->error_msg);
+  }
+}
+
+// This creates any ArtMethod* structures needed for obsolete methods and ensures that the stack is
+// updated so they will be run.
+bool Redefiner::FindAndAllocateObsoleteMethods(art::mirror::Class* art_klass) {
+  art::ScopedAssertNoThreadSuspension ns("No thread suspension during thread stack walking");
+  art::mirror::ClassExt* ext = art_klass->GetExtData();
+  CHECK(ext->GetObsoleteMethods() != nullptr);
+  CallbackCtx ctx(this, art_klass->GetClassLoader()->GetAllocator(), error_msg_);
+  // Add all the declared methods to the map
+  for (auto& m : art_klass->GetDeclaredMethods(art::kRuntimePointerSize)) {
+    ctx.obsolete_methods.insert(&m);
+  }
+  for (art::ArtMethod* old_method : ctx.obsolete_methods) {
+    if (old_method->IsIntrinsic()) {
+      *error_msg_ = StringPrintf("Method '%s' is intrinsic and cannot be made obsolete!",
+                                 old_method->PrettyMethod().c_str());
+      return false;
+    }
+  }
+  {
+    art::MutexLock mu(self_, *art::Locks::thread_list_lock_);
+    art::ThreadList* list = art::Runtime::Current()->GetThreadList();
+    list->ForEach(DoAllocateObsoleteMethodsCallback, static_cast<void*>(&ctx));
+    if (!ctx.success) {
+      list->ForEach(DoRestoreObsoleteMethodsCallback, static_cast<void*>(&ctx));
+      return false;
+    }
+  }
+  FillObsoleteMethodMap(art_klass, ctx.obsolete_map);
+  return true;
+}
+
+// Fills the obsolete method map in the art_klass's extData. This is so obsolete methods are able to
+// figure out their DexCaches.
+void Redefiner::FillObsoleteMethodMap(
+    art::mirror::Class* art_klass,
+    const std::unordered_map<art::ArtMethod*, art::ArtMethod*>& obsoletes) {
+  int32_t index = 0;
+  art::mirror::ClassExt* ext_data = art_klass->GetExtData();
+  art::mirror::PointerArray* obsolete_methods = ext_data->GetObsoleteMethods();
+  art::mirror::ObjectArray<art::mirror::DexCache>* obsolete_dex_caches =
+      ext_data->GetObsoleteDexCaches();
+  int32_t num_method_slots = obsolete_methods->GetLength();
+  // Find the first empty index.
+  for (; index < num_method_slots; index++) {
+    if (obsolete_methods->GetElementPtrSize<art::ArtMethod*>(
+          index, art::kRuntimePointerSize) == nullptr) {
+      break;
+    }
+  }
+  // Make sure we have enough space.
+  CHECK_GT(num_method_slots, static_cast<int32_t>(obsoletes.size() + index));
+  CHECK(obsolete_dex_caches->Get(index) == nullptr);
+  // Fill in the map.
+  for (auto& obs : obsoletes) {
+    obsolete_methods->SetElementPtrSize(index, obs.second, art::kRuntimePointerSize);
+    obsolete_dex_caches->Set(index, art_klass->GetDexCache());
+    index++;
+  }
+}
+
+// TODO It should be possible to only deoptimize the specific obsolete methods.
+// TODO ReJitEverything can (sort of) fail. In certain cases it will skip deoptimizing some frames.
+// If one of these frames is an obsolete method we have a problem. b/33616143
+// TODO This shouldn't be necessary once we can ensure that the current method is not kept in
+// registers across suspend points.
+// TODO Pending b/33630159
+void Redefiner::EnsureObsoleteMethodsAreDeoptimized() {
+  art::ScopedAssertNoThreadSuspension nts("Deoptimizing everything!");
+  art::instrumentation::Instrumentation* i = runtime_->GetInstrumentation();
+  i->ReJitEverything("libOpenJkdJvmti - Class Redefinition");
+}
+
 jvmtiError Redefiner::Run() {
   art::StackHandleScope<5> hs(self_);
   // TODO We might want to have a global lock (or one based on the class being redefined at least)
@@ -329,6 +575,13 @@
   }
   // Get the mirror class now that we aren't allocating anymore.
   art::Handle<art::mirror::Class> art_class(hs.NewHandle(GetMirrorClass()));
+  // Disable GC and wait for it to be done if we are a moving GC.  This is fine since we are done
+  // allocating so no deadlocks.
+  art::gc::Heap* heap = runtime_->GetHeap();
+  if (heap->IsGcConcurrentAndMoving()) {
+    // GC moving objects can cause deadlocks as we are deoptimizing the stack.
+    heap->IncrementDisableMovingGC(self_);
+  }
   // Enable assertion that this thread isn't interrupted during this installation.
   // After this we will need to do real cleanup in case of failure. Prior to this we could simply
   // return and would let everything get cleaned up or harmlessly leaked.
@@ -338,6 +591,11 @@
   self_->TransitionFromRunnableToSuspended(art::ThreadState::kNative);
   runtime_->GetThreadList()->SuspendAll(
       "Final installation of redefined Class!", /*long_suspend*/true);
+  // TODO We need to invalidate all breakpoints in the redefined class with the debugger.
+  // TODO We need to deal with any instrumentation/debugger deoptimized_methods_.
+  // TODO We need to update all debugger MethodIDs so they note the method they point to is
+  // obsolete or implement some other well defined semantics.
+  // TODO We need to decide on & implement semantics for JNI jmethodids when we redefine methods.
   // TODO Might want to move this into a different type.
   // Now we reach the part where we must do active cleanup if something fails.
   // TODO We should really Retry if this fails instead of simply aborting.
@@ -345,11 +603,15 @@
   art::ObjPtr<art::mirror::LongArray> original_dex_file_cookie(nullptr);
   if (!UpdateJavaDexFile(java_dex_file.Get(),
                          new_dex_file_cookie.Get(),
-                         &original_dex_file_cookie)) {
+                         &original_dex_file_cookie) ||
+      !FindAndAllocateObsoleteMethods(art_class.Get())) {
     // Release suspendAll
     runtime_->GetThreadList()->ResumeAll();
     // Get back shared mutator lock as expected for return.
     self_->TransitionFromSuspendedToRunnable();
+    if (heap->IsGcConcurrentAndMoving()) {
+      heap->DecrementDisableMovingGC(self_);
+    }
     return result_;
   }
   if (!UpdateClass(art_class.Get(), new_dex_cache.Get())) {
@@ -359,26 +621,34 @@
     runtime_->GetThreadList()->ResumeAll();
     // Get back shared mutator lock as expected for return.
     self_->TransitionFromSuspendedToRunnable();
+    if (heap->IsGcConcurrentAndMoving()) {
+      heap->DecrementDisableMovingGC(self_);
+    }
     return result_;
   }
-  // Update the ClassObjects Keep the old DexCache (and other stuff) around so we can restore
-  // functions/fields.
-  // Verify the new Class.
-  //   Failure then undo updates to class
-  // Do stack walks and allocate obsolete methods
-  // Shrink the obsolete method maps if possible?
-  // TODO find appropriate class loader. Allocate new dex files array. Pause all java treads.
-  // Replace dex files array. Do stack scan + allocate obsoletes. Remove array if possible.
-  // TODO We might want to ensure that all threads are stopped for this!
-  // AddDexToClassPath();
-  // TODO
-  // Release suspendAll
+  // Ensure that obsolete methods are deoptimized. This is needed since optimized methods may have
+  // pointers to their ArtMethod's stashed in registers that they then use to attempt to hit the
+  // DexCache.
+  // TODO This can fail (leave some methods optimized) near runtime methods (including
+  // quick-to-interpreter transition function).
+  // TODO We probably don't need this at all once we have a way to ensure that the
+  // current_art_method is never stashed in a (physical) register by the JIT and lost to the
+  // stack-walker.
+  EnsureObsoleteMethodsAreDeoptimized();
+  // TODO Verify the new Class.
+  // TODO   Failure then undo updates to class
+  // TODO Shrink the obsolete method maps if possible?
+  // TODO find appropriate class loader.
   // TODO Put this into a scoped thing.
   runtime_->GetThreadList()->ResumeAll();
   // Get back shared mutator lock as expected for return.
   self_->TransitionFromSuspendedToRunnable();
-  // TODO Do this at a more reasonable place.
+  // TODO Do the dex_file_ release at a more reasonable place. This works but it muddles who really
+  // owns the DexFile.
   dex_file_.release();
+  if (heap->IsGcConcurrentAndMoving()) {
+    heap->DecrementDisableMovingGC(self_);
+  }
   return OK;
 }
 
@@ -420,19 +690,24 @@
     }
     const art::DexFile::ProtoId* proto_id = dex_file_->FindProtoId(method_return_idx,
                                                                    new_type_list);
-    CHECK(proto_id != nullptr || old_type_list == nullptr);
     // TODO Return false, cleanup.
+    CHECK(proto_id != nullptr || old_type_list == nullptr);
     const art::DexFile::MethodId* method_id = dex_file_->FindMethodId(declaring_class_id,
                                                                       *new_name_id,
                                                                       *proto_id);
-    CHECK(method_id != nullptr);
     // TODO Return false, cleanup.
+    CHECK(method_id != nullptr);
     uint32_t dex_method_idx = dex_file_->GetIndexForMethodId(*method_id);
     method.SetDexMethodIndex(dex_method_idx);
     linker->SetEntryPointsToInterpreter(&method);
     method.SetCodeItemOffset(dex_file_->FindCodeItemOffset(class_def, dex_method_idx));
     method.SetDexCacheResolvedMethods(new_dex_cache->GetResolvedMethods(), image_pointer_size);
     method.SetDexCacheResolvedTypes(new_dex_cache->GetResolvedTypes(), image_pointer_size);
+    // Notify the jit that this method is redefined.
+    art::jit::Jit* jit = runtime_->GetJit();
+    if (jit != nullptr) {
+      jit->GetCodeCache()->NotifyMethodRedefined(&method);
+    }
   }
   return true;
 }
diff --git a/runtime/openjdkjvmti/ti_redefine.h b/runtime/openjdkjvmti/ti_redefine.h
index 73cfc2b..9d23ce4 100644
--- a/runtime/openjdkjvmti/ti_redefine.h
+++ b/runtime/openjdkjvmti/ti_redefine.h
@@ -64,6 +64,8 @@
 namespace openjdkjvmti {
 
 // Class that can redefine a single class's methods.
+// TODO We should really make this be driven by an outside class so we can do multiple classes at
+// the same time and have less required cleanup.
 class Redefiner {
  public:
   // Redefine the given class with the given dex data. Note this function does not take ownership of
@@ -124,6 +126,14 @@
   // in the future. For now we will just take the memory hit.
   bool EnsureClassAllocationsFinished() REQUIRES_SHARED(art::Locks::mutator_lock_);
 
+  // Ensure that obsolete methods are deoptimized. This is needed since optimized methods may have
+  // pointers to their ArtMethods stashed in registers that they then use to attempt to hit the
+  // DexCache.
+  void EnsureObsoleteMethodsAreDeoptimized()
+      REQUIRES(art::Locks::mutator_lock_)
+      REQUIRES(!art::Locks::thread_list_lock_,
+               !art::Locks::classlinker_classes_lock_);
+
   art::mirror::ClassLoader* GetClassLoader() REQUIRES_SHARED(art::Locks::mutator_lock_);
 
   // This finds the java.lang.DexFile we will add the native DexFile to as part of the classpath.
@@ -170,6 +180,13 @@
   bool UpdateClass(art::ObjPtr<art::mirror::Class> mclass,
                    art::ObjPtr<art::mirror::DexCache> new_dex_cache)
       REQUIRES(art::Locks::mutator_lock_);
+
+  bool FindAndAllocateObsoleteMethods(art::mirror::Class* art_klass)
+      REQUIRES(art::Locks::mutator_lock_);
+
+  void FillObsoleteMethodMap(art::mirror::Class* art_klass,
+                             const std::unordered_map<art::ArtMethod*, art::ArtMethod*>& obsoletes)
+      REQUIRES(art::Locks::mutator_lock_);
 };
 
 }  // namespace openjdkjvmti
diff --git a/runtime/runtime.h b/runtime/runtime.h
index d40c631..8fc211c 100644
--- a/runtime/runtime.h
+++ b/runtime/runtime.h
@@ -268,7 +268,7 @@
     return java_vm_.get();
   }
 
-  size_t GetMaxSpinsBeforeThinkLockInflation() const {
+  size_t GetMaxSpinsBeforeThinLockInflation() const {
     return max_spins_before_thin_lock_inflation_;
   }
 
diff --git a/runtime/stack.cc b/runtime/stack.cc
index 3fed7c9..f9efc0b 100644
--- a/runtime/stack.cc
+++ b/runtime/stack.cc
@@ -618,6 +618,17 @@
   return result;
 }
 
+void StackVisitor::SetMethod(ArtMethod* method) {
+  DCHECK(GetMethod() != nullptr);
+  if (cur_shadow_frame_ != nullptr) {
+    cur_shadow_frame_->SetMethod(method);
+  } else {
+    DCHECK(cur_quick_frame_ != nullptr);
+    CHECK(!IsInInlinedFrame()) << "We do not support setting inlined method's ArtMethod!";
+      *cur_quick_frame_ = method;
+  }
+}
+
 static void AssertPcIsWithinQuickCode(ArtMethod* method, uintptr_t pc)
     REQUIRES_SHARED(Locks::mutator_lock_) {
   if (method->IsNative() || method->IsRuntimeMethod() || method->IsProxyMethod()) {
diff --git a/runtime/stack.h b/runtime/stack.h
index b1e99e5..9dceb29 100644
--- a/runtime/stack.h
+++ b/runtime/stack.h
@@ -327,6 +327,12 @@
     }
   }
 
+  void SetMethod(ArtMethod* method) REQUIRES(Locks::mutator_lock_) {
+    DCHECK(method != nullptr);
+    DCHECK(method_ != nullptr);
+    method_ = method;
+  }
+
   ArtMethod* GetMethod() const REQUIRES_SHARED(Locks::mutator_lock_) {
     DCHECK(method_ != nullptr);
     return method_;
@@ -610,6 +616,10 @@
 
   ArtMethod* GetMethod() const REQUIRES_SHARED(Locks::mutator_lock_);
 
+  // Sets this stack frame's method pointer. This requires a full lock of the MutatorLock. This
+  // doesn't work with inlined methods.
+  void SetMethod(ArtMethod* method) REQUIRES(Locks::mutator_lock_);
+
   ArtMethod* GetOuterMethod() const {
     return *GetCurrentQuickFrame();
   }
diff --git a/runtime/stack_map.h b/runtime/stack_map.h
index dd7e531..5e556be 100644
--- a/runtime/stack_map.h
+++ b/runtime/stack_map.h
@@ -1093,7 +1093,9 @@
   }
 
   CodeInfoEncoding ExtractEncoding() const {
-    return CodeInfoEncoding(region_.start());
+    CodeInfoEncoding encoding(region_.start());
+    AssertValidStackMap(encoding);
+    return encoding;
   }
 
   bool HasInlineInfo(const CodeInfoEncoding& encoding) const {
@@ -1254,6 +1256,18 @@
             uint16_t number_of_dex_registers,
             bool dump_stack_maps) const;
 
+  // Check that the code info has valid stack map and abort if it does not.
+  void AssertValidStackMap(const CodeInfoEncoding& encoding) const {
+    if (region_.size() != 0 && region_.size() < GetStackMapsSize(encoding)) {
+      LOG(FATAL) << region_.size() << "\n"
+                 << encoding.header_size << "\n"
+                 << encoding.non_header_size << "\n"
+                 << encoding.number_of_location_catalog_entries << "\n"
+                 << encoding.number_of_stack_maps << "\n"
+                 << encoding.stack_map_size_in_bytes;
+    }
+  }
+
  private:
   MemoryRegion GetStackMaps(const CodeInfoEncoding& encoding) const {
     return region_.size() == 0
diff --git a/runtime/verifier/verifier_deps.cc b/runtime/verifier/verifier_deps.cc
index 5f94a1b..15cc566 100644
--- a/runtime/verifier/verifier_deps.cc
+++ b/runtime/verifier/verifier_deps.cc
@@ -245,18 +245,18 @@
 bool VerifierDeps::IsInClassPath(ObjPtr<mirror::Class> klass) const {
   DCHECK(klass != nullptr);
 
-  ObjPtr<mirror::DexCache> dex_cache = klass->GetDexCache();
-  if (dex_cache == nullptr) {
-    // This is a synthesized class, in this case always an array. They are not
-    // defined in the compiled DEX files and therefore are part of the classpath.
-    // We do not record dependencies on arrays with component types in
-    // the compiled DEX files, as the only thing that might change is their
-    // access flags. If we were to change these flags in a breaking way, we would
-    // need to enforce full verification again anyways by updating the vdex version.
-    DCHECK(klass->IsArrayClass()) << klass->PrettyDescriptor();
-    return false;
+  // For array types, we return whether the non-array component type
+  // is in the classpath.
+  while (klass->IsArrayClass()) {
+    klass = klass->GetComponentType();
   }
 
+  if (klass->IsPrimitive()) {
+    return true;
+  }
+
+  ObjPtr<mirror::DexCache> dex_cache = klass->GetDexCache();
+  DCHECK(dex_cache != nullptr);
   const DexFile* dex_file = dex_cache->GetDexFile();
   DCHECK(dex_file != nullptr);
 
@@ -335,6 +335,60 @@
   }
 }
 
+mirror::Class* VerifierDeps::FindOneClassPathBoundaryForInterface(mirror::Class* destination,
+                                                                  mirror::Class* source) const {
+  DCHECK(destination->IsInterface());
+  DCHECK(IsInClassPath(destination));
+  Thread* thread = Thread::Current();
+  mirror::Class* current = source;
+  // Record the classes that are at the boundary between the compiled DEX files and
+  // the classpath. We will check those classes later to find one class that inherits
+  // `destination`.
+  std::vector<ObjPtr<mirror::Class>> boundaries;
+  // If the destination is a direct interface of a class defined in the DEX files being
+  // compiled, no need to record it.
+  while (!IsInClassPath(current)) {
+    for (size_t i = 0; i < current->NumDirectInterfaces(); ++i) {
+      ObjPtr<mirror::Class> direct = mirror::Class::GetDirectInterface(thread, current, i);
+      if (direct == destination) {
+        return nullptr;
+      } else if (IsInClassPath(direct)) {
+        boundaries.push_back(direct);
+      }
+    }
+    current = current->GetSuperClass();
+  }
+  DCHECK(current != nullptr);
+  boundaries.push_back(current);
+
+  // Check if we have an interface defined in the DEX files being compiled, direclty
+  // inheriting `destination`.
+  int32_t iftable_count = source->GetIfTableCount();
+  ObjPtr<mirror::IfTable> iftable = source->GetIfTable();
+  for (int32_t i = 0; i < iftable_count; ++i) {
+    mirror::Class* itf = iftable->GetInterface(i);
+    if (!IsInClassPath(itf)) {
+      for (size_t j = 0; j < itf->NumDirectInterfaces(); ++j) {
+        ObjPtr<mirror::Class> direct = mirror::Class::GetDirectInterface(thread, itf, j);
+        if (direct == destination) {
+          return nullptr;
+        } else if (IsInClassPath(direct)) {
+          boundaries.push_back(direct);
+        }
+      }
+    }
+  }
+
+  // Find a boundary making `source` inherit from `destination`. We must find one.
+  for (const ObjPtr<mirror::Class>& boundary : boundaries) {
+    if (destination->IsAssignableFrom(boundary)) {
+      return boundary.Ptr();
+    }
+  }
+  LOG(FATAL) << "Should have found a classpath boundary";
+  UNREACHABLE();
+}
+
 void VerifierDeps::AddAssignability(const DexFile& dex_file,
                                     mirror::Class* destination,
                                     mirror::Class* source,
@@ -403,17 +457,26 @@
     return;
   }
 
-  if (!IsInClassPath(source) && !source->IsInterface() && !destination->IsInterface()) {
-    // Find the super class at the classpath boundary. Only that class
-    // can change the assignability.
-    // TODO: also chase the boundary for interfaces.
-    do {
-      source = source->GetSuperClass();
-    } while (!IsInClassPath(source));
+  if (!IsInClassPath(source)) {
+    if (!destination->IsInterface()) {
+      DCHECK(!source->IsInterface());
+      // Find the super class at the classpath boundary. Only that class
+      // can change the assignability.
+      do {
+        source = source->GetSuperClass();
+      } while (!IsInClassPath(source));
 
-    // If that class is the actual destination, no need to record it.
-    if (source == destination) {
-      return;
+      // If that class is the actual destination, no need to record it.
+      if (source == destination) {
+        return;
+      }
+    } else if (is_assignable) {
+      source = FindOneClassPathBoundaryForInterface(destination, source);
+      if (source == nullptr) {
+        // There was no classpath boundary, no need to record.
+        return;
+      }
+      DCHECK(IsInClassPath(source));
     }
   }
 
diff --git a/runtime/verifier/verifier_deps.h b/runtime/verifier/verifier_deps.h
index 4b8206f..11750fd 100644
--- a/runtime/verifier/verifier_deps.h
+++ b/runtime/verifier/verifier_deps.h
@@ -204,6 +204,13 @@
   bool IsInClassPath(ObjPtr<mirror::Class> klass) const
       REQUIRES_SHARED(Locks::mutator_lock_);
 
+  // Finds the class in the classpath that makes `source` inherit` from `destination`.
+  // Returns null if a class defined in the compiled DEX files, and assignable to
+  // `source`, direclty inherits from `destination`.
+  mirror::Class* FindOneClassPathBoundaryForInterface(mirror::Class* destination,
+                                                      mirror::Class* source) const
+      REQUIRES_SHARED(Locks::mutator_lock_);
+
   // Returns the index of `str`. If it is defined in `dex_file_`, this is the dex
   // string ID. If not, an ID is assigned to the string and cached in `strings_`
   // of the corresponding DexFileDeps structure (either provided or inferred from
diff --git a/runtime/well_known_classes.cc b/runtime/well_known_classes.cc
index 009170c..7b5ced1 100644
--- a/runtime/well_known_classes.cc
+++ b/runtime/well_known_classes.cc
@@ -52,6 +52,7 @@
 jclass WellKnownClasses::java_lang_Daemons;
 jclass WellKnownClasses::java_lang_Error;
 jclass WellKnownClasses::java_lang_ExceptionInInitializerError;
+jclass WellKnownClasses::java_lang_invoke_MethodHandle;
 jclass WellKnownClasses::java_lang_IllegalAccessError;
 jclass WellKnownClasses::java_lang_NoClassDefFoundError;
 jclass WellKnownClasses::java_lang_Object;
@@ -93,6 +94,8 @@
 jmethodID WellKnownClasses::java_lang_Double_valueOf;
 jmethodID WellKnownClasses::java_lang_Float_valueOf;
 jmethodID WellKnownClasses::java_lang_Integer_valueOf;
+jmethodID WellKnownClasses::java_lang_invoke_MethodHandle_invoke;
+jmethodID WellKnownClasses::java_lang_invoke_MethodHandle_invokeExact;
 jmethodID WellKnownClasses::java_lang_Long_valueOf;
 jmethodID WellKnownClasses::java_lang_ref_FinalizerReference_add;
 jmethodID WellKnownClasses::java_lang_ref_ReferenceQueue_add;
@@ -288,6 +291,7 @@
   java_lang_Error = CacheClass(env, "java/lang/Error");
   java_lang_ExceptionInInitializerError = CacheClass(env, "java/lang/ExceptionInInitializerError");
   java_lang_IllegalAccessError = CacheClass(env, "java/lang/IllegalAccessError");
+  java_lang_invoke_MethodHandle = CacheClass(env, "java/lang/invoke/MethodHandle");
   java_lang_NoClassDefFoundError = CacheClass(env, "java/lang/NoClassDefFoundError");
   java_lang_reflect_Constructor = CacheClass(env, "java/lang/reflect/Constructor");
   java_lang_reflect_Executable = CacheClass(env, "java/lang/reflect/Executable");
@@ -321,7 +325,12 @@
   java_lang_Daemons_requestHeapTrim = CacheMethod(env, java_lang_Daemons, true, "requestHeapTrim", "()V");
   java_lang_Daemons_start = CacheMethod(env, java_lang_Daemons, true, "start", "()V");
   java_lang_Daemons_stop = CacheMethod(env, java_lang_Daemons, true, "stop", "()V");
-
+  java_lang_invoke_MethodHandle_invoke =
+      CacheMethod(env, java_lang_invoke_MethodHandle, false,
+                  "invoke", "([Ljava/lang/Object;)Ljava/lang/Object;");
+  java_lang_invoke_MethodHandle_invokeExact =
+      CacheMethod(env, java_lang_invoke_MethodHandle, false,
+                  "invokeExact", "([Ljava/lang/Object;)Ljava/lang/Object;");
   ScopedLocalRef<jclass> java_lang_ref_FinalizerReference(env, env->FindClass("java/lang/ref/FinalizerReference"));
   java_lang_ref_FinalizerReference_add = CacheMethod(env, java_lang_ref_FinalizerReference.get(), true, "add", "(Ljava/lang/Object;)V");
   ScopedLocalRef<jclass> java_lang_ref_ReferenceQueue(env, env->FindClass("java/lang/ref/ReferenceQueue"));
diff --git a/runtime/well_known_classes.h b/runtime/well_known_classes.h
index 227996a..371be61 100644
--- a/runtime/well_known_classes.h
+++ b/runtime/well_known_classes.h
@@ -63,6 +63,7 @@
   static jclass java_lang_Error;
   static jclass java_lang_ExceptionInInitializerError;
   static jclass java_lang_IllegalAccessError;
+  static jclass java_lang_invoke_MethodHandle;
   static jclass java_lang_NoClassDefFoundError;
   static jclass java_lang_Object;
   static jclass java_lang_OutOfMemoryError;
@@ -103,6 +104,8 @@
   static jmethodID java_lang_Double_valueOf;
   static jmethodID java_lang_Float_valueOf;
   static jmethodID java_lang_Integer_valueOf;
+  static jmethodID java_lang_invoke_MethodHandle_invoke;
+  static jmethodID java_lang_invoke_MethodHandle_invokeExact;
   static jmethodID java_lang_Long_valueOf;
   static jmethodID java_lang_ref_FinalizerReference_add;
   static jmethodID java_lang_ref_ReferenceQueue_add;
diff --git a/test/631-checker-fp-abs/expected.txt b/test/631-checker-fp-abs/expected.txt
new file mode 100644
index 0000000..b0aad4d
--- /dev/null
+++ b/test/631-checker-fp-abs/expected.txt
@@ -0,0 +1 @@
+passed
diff --git a/test/631-checker-fp-abs/info.txt b/test/631-checker-fp-abs/info.txt
new file mode 100644
index 0000000..0a1499e
--- /dev/null
+++ b/test/631-checker-fp-abs/info.txt
@@ -0,0 +1 @@
+Tests on floating-point Math.abs.
diff --git a/test/631-checker-fp-abs/src/Main.java b/test/631-checker-fp-abs/src/Main.java
new file mode 100644
index 0000000..0f85dc6
--- /dev/null
+++ b/test/631-checker-fp-abs/src/Main.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * A few tests of Math.abs for floating-point data.
+ *
+ * Note, as a "quality of implementation", rather than pure "spec compliance",
+ * we require that Math.abs() clears the sign bit (but changes nothing else)
+ * for all numbers, including NaN (signaling NaN may become quiet though).
+ */
+public class Main {
+
+  private static final int SPQUIET = 1 << 22;
+  private static final long DPQUIET = 1L << 51;
+
+  public static boolean doThrow = false;
+
+  /// CHECK-START: float Main.$opt$noinline$absSP(float) intrinsics_recognition (after)
+  /// CHECK-DAG: <<Result:f\d+>> InvokeStaticOrDirect intrinsic:MathAbsFloat
+  /// CHECK-DAG:                 Return [<<Result>>]
+  private static float $opt$noinline$absSP(float f) {
+    if (doThrow) {
+      throw new Error("Something to prevent inlining");
+    }
+    return Math.abs(f);
+  }
+
+  /// CHECK-START: double Main.$opt$noinline$absDP(double) intrinsics_recognition (after)
+  /// CHECK-DAG: <<Result:d\d+>> InvokeStaticOrDirect intrinsic:MathAbsDouble
+  /// CHECK-DAG:                 Return [<<Result>>]
+  private static double $opt$noinline$absDP(double d) {
+    if (doThrow) {
+      throw new Error("Something to prevent inlining");
+    }
+    return Math.abs(d);
+  }
+
+  public static void main(String args[]) {
+    // A few obvious numbers.
+    for (float f = -100.0f; f < 0.0f; f += 0.5f) {
+      expectEqualsSP(-f, $opt$noinline$absSP(f));
+    }
+    for (float f = 0.0f; f <= 100.0f; f += 0.5f) {
+      expectEqualsSP(f, $opt$noinline$absSP(f));
+    }
+    for (float f = -1.5f; f <= -1.499f; f = Math.nextAfter(f, Float.POSITIVE_INFINITY)) {
+      expectEqualsSP(-f, $opt$noinline$absSP(f));
+    }
+    for (float f = 1.499f; f <= 1.5f; f = Math.nextAfter(f, Float.POSITIVE_INFINITY)) {
+      expectEqualsSP(f, $opt$noinline$absSP(f));
+    }
+
+    // Zero
+    expectEquals32(0, Float.floatToRawIntBits($opt$noinline$absSP(+0.0f)));
+    expectEquals32(0, Float.floatToRawIntBits($opt$noinline$absSP(-0.0f)));
+
+    // Inf.
+    expectEqualsSP(Float.POSITIVE_INFINITY, $opt$noinline$absSP(Float.NEGATIVE_INFINITY));
+    expectEqualsSP(Float.POSITIVE_INFINITY, $opt$noinline$absSP(Float.POSITIVE_INFINITY));
+
+    // A few NaN numbers.
+    int[] spnans = {
+      0x7f800001,
+      0x7fa00000,
+      0x7fc00000,
+      0x7fffffff,
+      0xff800001,
+      0xffa00000,
+      0xffc00000,
+      0xffffffff
+    };
+    for (int i = 0; i < spnans.length; i++) {
+      float f = Float.intBitsToFloat(spnans[i]);
+      expectEqualsNaN32(
+          spnans[i] & Integer.MAX_VALUE,
+          Float.floatToRawIntBits($opt$noinline$absSP(f)));
+    }
+
+    // A few obvious numbers.
+    for (double d = -100.0; d < 0.0; d += 0.5) {
+      expectEqualsDP(-d, $opt$noinline$absDP(d));
+    }
+    for (double d = 0.0; d <= 100.0; d += 0.5) {
+      expectEqualsDP(d, $opt$noinline$absDP(d));
+    }
+    for (double d = -1.5d; d <= -1.49999999999d; d = Math.nextAfter(d, Double.POSITIVE_INFINITY)) {
+      expectEqualsDP(-d, $opt$noinline$absDP(d));
+    }
+    for (double d = 1.49999999999d; d <= 1.5; d = Math.nextAfter(d, Double.POSITIVE_INFINITY)) {
+      expectEqualsDP(d, $opt$noinline$absDP(d));
+    }
+
+    // Zero
+    expectEquals64(0L, Double.doubleToRawLongBits($opt$noinline$absDP(+0.0f)));
+    expectEquals64(0L, Double.doubleToRawLongBits($opt$noinline$absDP(-0.0f)));
+
+    // Inf.
+    expectEqualsDP(Double.POSITIVE_INFINITY, $opt$noinline$absDP(Double.NEGATIVE_INFINITY));
+    expectEqualsDP(Double.POSITIVE_INFINITY, $opt$noinline$absDP(Double.POSITIVE_INFINITY));
+
+    // A few NaN numbers.
+    long[] dpnans = {
+      0x7ff0000000000001L,
+      0x7ff4000000000000L,
+      0x7ff8000000000000L,
+      0x7fffffffffffffffL,
+      0xfff0000000000001L,
+      0xfff4000000000000L,
+      0xfff8000000000000L,
+      0xffffffffffffffffL
+    };
+    for (int i = 0; i < dpnans.length; i++) {
+      double d = Double.longBitsToDouble(dpnans[i]);
+      expectEqualsNaN64(
+          dpnans[i] & Long.MAX_VALUE,
+          Double.doubleToRawLongBits($opt$noinline$absDP(d)));
+    }
+
+    System.out.println("passed");
+  }
+
+  private static void expectEquals32(int expected, int result) {
+    if (expected != result) {
+      throw new Error("Expected: 0x" + Integer.toHexString(expected)
+          + ", found: 0x" + Integer.toHexString(result));
+    }
+  }
+
+  // We allow that an expected NaN result has become quiet.
+  private static void expectEqualsNaN32(int expected, int result) {
+    if (expected != result && (expected | SPQUIET) != result) {
+      throw new Error("Expected: 0x" + Integer.toHexString(expected)
+          + ", found: 0x" + Integer.toHexString(result));
+    }
+  }
+
+  private static void expectEquals64(long expected, long result) {
+    if (expected != result) {
+      throw new Error("Expected: 0x" + Long.toHexString(expected)
+          + ", found: 0x" + Long.toHexString(result));
+    }
+  }
+
+  // We allow that an expected NaN result has become quiet.
+  private static void expectEqualsNaN64(long expected, long result) {
+    if (expected != result && (expected | DPQUIET) != result) {
+      throw new Error("Expected: 0x" + Long.toHexString(expected)
+          + ", found: 0x" + Long.toHexString(result));
+    }
+  }
+
+  private static void expectEqualsSP(float expected, float result) {
+    if (expected != result) {
+      throw new Error("Expected: " + expected + ", found: " + result);
+    }
+  }
+
+  private static void expectEqualsDP(double expected, double result) {
+    if (expected != result) {
+      throw new Error("Expected: " + expected + ", found: " + result);
+    }
+  }
+}
diff --git a/test/901-hello-ti-agent/run b/test/901-hello-ti-agent/run
index 8079a8c..4379349 100755
--- a/test/901-hello-ti-agent/run
+++ b/test/901-hello-ti-agent/run
@@ -14,14 +14,6 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-plugin=libopenjdkjvmtid.so
-agent=libtiagentd.so
-if  [[ "$@" == *"-O"* ]]; then
-  agent=libtiagent.so
-  plugin=libopenjdkjvmti.so
-fi
-
 ./default-run "$@" --experimental agents \
                    --experimental runtime-plugins \
-                   --runtime-option -agentpath:${agent}=901-hello-ti-agent \
-                   --android-runtime-option -Xplugin:${plugin}
+                   --jvmti
diff --git a/test/902-hello-transformation/run b/test/902-hello-transformation/run
index 94a8b2d..4379349 100755
--- a/test/902-hello-transformation/run
+++ b/test/902-hello-transformation/run
@@ -14,30 +14,6 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-plugin=libopenjdkjvmtid.so
-agent=libtiagentd.so
-lib=tiagentd
-if  [[ "$@" == *"-O"* ]]; then
-  agent=libtiagent.so
-  plugin=libopenjdkjvmti.so
-  lib=tiagent
-fi
-
-if [[ "$@" == *"--jvm"* ]]; then
-  arg="jvm"
-else
-  arg="art"
-  if [[ "$@" != *"--debuggable"* ]]; then
-    other_args=" -Xcompiler-option --debuggable "
-  else
-    other_args=""
-  fi
-fi
-
 ./default-run "$@" --experimental agents \
                    --experimental runtime-plugins \
-                   --runtime-option -agentpath:${agent}=902-hello-transformation,${arg} \
-                   --android-runtime-option -Xplugin:${plugin} \
-                   --android-runtime-option -Xfully-deoptable \
-                   ${other_args} \
-                   --args ${lib}
+                   --jvmti
diff --git a/test/903-hello-tagging/run b/test/903-hello-tagging/run
index 5e3c0bd..4379349 100755
--- a/test/903-hello-tagging/run
+++ b/test/903-hello-tagging/run
@@ -14,30 +14,6 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-plugin=libopenjdkjvmtid.so
-agent=libtiagentd.so
-lib=tiagentd
-if  [[ "$@" == *"-O"* ]]; then
-  agent=libtiagent.so
-  plugin=libopenjdkjvmti.so
-  lib=tiagent
-fi
-
-if [[ "$@" == *"--jvm"* ]]; then
-  arg="jvm"
-else
-  arg="art"
-fi
-
-if [[ "$@" != *"--debuggable"* ]]; then
-  other_args=" -Xcompiler-option --debuggable "
-else
-  other_args=""
-fi
-
 ./default-run "$@" --experimental agents \
                    --experimental runtime-plugins \
-                   --runtime-option -agentpath:${agent}=903-hello-tagging,${arg} \
-                   --android-runtime-option -Xplugin:${plugin} \
-                   ${other_args} \
-                   --args ${lib}
+                   --jvmti
diff --git a/test/903-hello-tagging/tagging.cc b/test/903-hello-tagging/tagging.cc
index 1557d45..60a31bd 100644
--- a/test/903-hello-tagging/tagging.cc
+++ b/test/903-hello-tagging/tagging.cc
@@ -44,6 +44,7 @@
     char* err;
     jvmti_env->GetErrorName(ret, &err);
     printf("Error setting tag: %s\n", err);
+    jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(err));
   }
 }
 
@@ -56,6 +57,7 @@
     char* err;
     jvmti_env->GetErrorName(ret, &err);
     printf("Error getting tag: %s\n", err);
+    jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(err));
   }
   return tag;
 }
@@ -90,6 +92,7 @@
     char* err;
     jvmti_env->GetErrorName(ret, &err);
     printf("Failure running GetLoadedClasses: %s\n", err);
+    jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(err));
     return nullptr;
   }
 
diff --git a/test/904-object-allocation/run b/test/904-object-allocation/run
index 2f7ad21..4379349 100755
--- a/test/904-object-allocation/run
+++ b/test/904-object-allocation/run
@@ -14,30 +14,6 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-plugin=libopenjdkjvmtid.so
-agent=libtiagentd.so
-lib=tiagentd
-if  [[ "$@" == *"-O"* ]]; then
-  agent=libtiagent.so
-  plugin=libopenjdkjvmti.so
-  lib=tiagent
-fi
-
-if [[ "$@" == *"--jvm"* ]]; then
-  arg="jvm"
-else
-  arg="art"
-fi
-
-if [[ "$@" != *"--debuggable"* ]]; then
-  other_args=" -Xcompiler-option --debuggable "
-else
-  other_args=""
-fi
-
 ./default-run "$@" --experimental agents \
                    --experimental runtime-plugins \
-                   --runtime-option -agentpath:${agent}=904-object-allocation,${arg} \
-                   --android-runtime-option -Xplugin:${plugin} \
-                   ${other_args} \
-                   --args ${lib}
+                   --jvmti
diff --git a/test/904-object-allocation/tracking.cc b/test/904-object-allocation/tracking.cc
index 9261a9f..f993606 100644
--- a/test/904-object-allocation/tracking.cc
+++ b/test/904-object-allocation/tracking.cc
@@ -69,6 +69,7 @@
     char* err;
     jvmti_env->GetErrorName(ret, &err);
     printf("Error setting callbacks: %s\n", err);
+    jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(err));
   }
 }
 
@@ -84,6 +85,7 @@
     char* err;
     jvmti_env->GetErrorName(ret, &err);
     printf("Error enabling/disabling allocation tracking: %s\n", err);
+    jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(err));
   }
 }
 
diff --git a/test/905-object-free/run b/test/905-object-free/run
index 753b742..4379349 100755
--- a/test/905-object-free/run
+++ b/test/905-object-free/run
@@ -14,30 +14,6 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-plugin=libopenjdkjvmtid.so
-agent=libtiagentd.so
-lib=tiagentd
-if  [[ "$@" == *"-O"* ]]; then
-  agent=libtiagent.so
-  plugin=libopenjdkjvmti.so
-  lib=tiagent
-fi
-
-if [[ "$@" == *"--jvm"* ]]; then
-  arg="jvm"
-else
-  arg="art"
-fi
-
-if [[ "$@" != *"--debuggable"* ]]; then
-  other_args=" -Xcompiler-option --debuggable "
-else
-  other_args=""
-fi
-
 ./default-run "$@" --experimental agents \
                    --experimental runtime-plugins \
-                   --runtime-option -agentpath:${agent}=905-object-free,${arg} \
-                   --android-runtime-option -Xplugin:${plugin} \
-                   ${other_args} \
-                   --args ${lib}
+                   --jvmti
diff --git a/test/905-object-free/tracking_free.cc b/test/905-object-free/tracking_free.cc
index fc43acc..7f295ac 100644
--- a/test/905-object-free/tracking_free.cc
+++ b/test/905-object-free/tracking_free.cc
@@ -50,6 +50,7 @@
     char* err;
     jvmti_env->GetErrorName(ret, &err);
     printf("Error setting callbacks: %s\n", err);
+    jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(err));
   }
 }
 
@@ -64,6 +65,7 @@
     char* err;
     jvmti_env->GetErrorName(ret, &err);
     printf("Error enabling/disabling object-free callbacks: %s\n", err);
+    jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(err));
   }
 }
 
diff --git a/test/906-iterate-heap/iterate_heap.cc b/test/906-iterate-heap/iterate_heap.cc
index 8dac89d..a2fd591 100644
--- a/test/906-iterate-heap/iterate_heap.cc
+++ b/test/906-iterate-heap/iterate_heap.cc
@@ -61,6 +61,7 @@
     char* err;
     jvmti_env->GetErrorName(ret, &err);
     printf("Failure running IterateThroughHeap: %s\n", err);
+    jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(err));
     return false;
   }
   return true;
diff --git a/test/906-iterate-heap/run b/test/906-iterate-heap/run
index 3e135a3..4379349 100755
--- a/test/906-iterate-heap/run
+++ b/test/906-iterate-heap/run
@@ -14,30 +14,6 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-plugin=libopenjdkjvmtid.so
-agent=libtiagentd.so
-lib=tiagentd
-if  [[ "$@" == *"-O"* ]]; then
-  agent=libtiagent.so
-  plugin=libopenjdkjvmti.so
-  lib=tiagent
-fi
-
-if [[ "$@" == *"--jvm"* ]]; then
-  arg="jvm"
-else
-  arg="art"
-fi
-
-if [[ "$@" != *"--debuggable"* ]]; then
-  other_args=" -Xcompiler-option --debuggable "
-else
-  other_args=""
-fi
-
 ./default-run "$@" --experimental agents \
                    --experimental runtime-plugins \
-                   --runtime-option -agentpath:${agent}=906-iterate-heap,${arg} \
-                   --android-runtime-option -Xplugin:${plugin} \
-                   ${other_args} \
-                   --args ${lib}
+                   --jvmti
diff --git a/test/907-get-loaded-classes/get_loaded_classes.cc b/test/907-get-loaded-classes/get_loaded_classes.cc
index afbb774..36d33b6 100644
--- a/test/907-get-loaded-classes/get_loaded_classes.cc
+++ b/test/907-get-loaded-classes/get_loaded_classes.cc
@@ -48,6 +48,7 @@
     char* err;
     jvmti_env->GetErrorName(result, &err);
     printf("Failure running GetLoadedClasses: %s\n", err);
+    jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(err));
     return nullptr;
   }
 
diff --git a/test/907-get-loaded-classes/run b/test/907-get-loaded-classes/run
index 3f5a059..4379349 100755
--- a/test/907-get-loaded-classes/run
+++ b/test/907-get-loaded-classes/run
@@ -14,30 +14,6 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-plugin=libopenjdkjvmtid.so
-agent=libtiagentd.so
-lib=tiagentd
-if  [[ "$@" == *"-O"* ]]; then
-  agent=libtiagent.so
-  plugin=libopenjdkjvmti.so
-  lib=tiagent
-fi
-
-if [[ "$@" == *"--jvm"* ]]; then
-  arg="jvm"
-else
-  arg="art"
-fi
-
-if [[ "$@" != *"--debuggable"* ]]; then
-  other_args=" -Xcompiler-option --debuggable "
-else
-  other_args=""
-fi
-
 ./default-run "$@" --experimental agents \
                    --experimental runtime-plugins \
-                   --runtime-option -agentpath:${agent}=907-get-loaded-classes,${arg} \
-                   --android-runtime-option -Xplugin:${plugin} \
-                   ${other_args} \
-                   --args ${lib}
+                   --jvmti
diff --git a/test/908-gc-start-finish/gc_callbacks.cc b/test/908-gc-start-finish/gc_callbacks.cc
index 771d1ad..1fab79d 100644
--- a/test/908-gc-start-finish/gc_callbacks.cc
+++ b/test/908-gc-start-finish/gc_callbacks.cc
@@ -51,6 +51,7 @@
     char* err;
     jvmti_env->GetErrorName(ret, &err);
     printf("Error setting callbacks: %s\n", err);
+    jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(err));
   }
 }
 
@@ -65,6 +66,7 @@
     char* err;
     jvmti_env->GetErrorName(ret, &err);
     printf("Error enabling/disabling gc callbacks: %s\n", err);
+    jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(err));
   }
   ret = jvmti_env->SetEventNotificationMode(
       enable ? JVMTI_ENABLE : JVMTI_DISABLE,
@@ -74,6 +76,7 @@
     char* err;
     jvmti_env->GetErrorName(ret, &err);
     printf("Error enabling/disabling gc callbacks: %s\n", err);
+    jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(err));
   }
 }
 
diff --git a/test/908-gc-start-finish/run b/test/908-gc-start-finish/run
index 2fc35f0..4379349 100755
--- a/test/908-gc-start-finish/run
+++ b/test/908-gc-start-finish/run
@@ -14,30 +14,6 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-plugin=libopenjdkjvmtid.so
-agent=libtiagentd.so
-lib=tiagentd
-if  [[ "$@" == *"-O"* ]]; then
-  agent=libtiagent.so
-  plugin=libopenjdkjvmti.so
-  lib=tiagent
-fi
-
-if [[ "$@" == *"--jvm"* ]]; then
-  arg="jvm"
-else
-  arg="art"
-fi
-
-if [[ "$@" != *"--debuggable"* ]]; then
-  other_args=" -Xcompiler-option --debuggable "
-else
-  other_args=""
-fi
-
 ./default-run "$@" --experimental agents \
                    --experimental runtime-plugins \
-                   --runtime-option -agentpath:${agent}=908-gc-start-finish,${arg} \
-                   --android-runtime-option -Xplugin:${plugin} \
-                   ${other_args} \
-                   --args ${lib}
+                   --jvmti
diff --git a/test/910-methods/expected.txt b/test/910-methods/expected.txt
index 9a74799..539f85b 100644
--- a/test/910-methods/expected.txt
+++ b/test/910-methods/expected.txt
@@ -1,15 +1,35 @@
 [toString, ()Ljava/lang/String;, null]
 class java.lang.Object
 1
+Max locals: 3
+Argument size: 1
+Location start: 0
+Location end: 40
 [charAt, (I)C, null]
 class java.lang.String
 257
+Max locals: JVMTI_ERROR_NATIVE_METHOD
+Argument size: JVMTI_ERROR_NATIVE_METHOD
+Location start: JVMTI_ERROR_NATIVE_METHOD
+Location end: JVMTI_ERROR_NATIVE_METHOD
 [sqrt, (D)D, null]
 class java.lang.Math
 265
+Max locals: JVMTI_ERROR_NATIVE_METHOD
+Argument size: JVMTI_ERROR_NATIVE_METHOD
+Location start: JVMTI_ERROR_NATIVE_METHOD
+Location end: JVMTI_ERROR_NATIVE_METHOD
 [add, (Ljava/lang/Object;)Z, null]
 interface java.util.List
 1025
+Max locals: 0
+Argument size: 0
+Location start: 0
+Location end: 0
 [run, ()V, null]
 class $Proxy0
 17
+Max locals: 0
+Argument size: 0
+Location start: 0
+Location end: 0
diff --git a/test/910-methods/methods.cc b/test/910-methods/methods.cc
index 3ed91d7..d3e4987 100644
--- a/test/910-methods/methods.cc
+++ b/test/910-methods/methods.cc
@@ -41,6 +41,7 @@
     char* err;
     jvmti_env->GetErrorName(result, &err);
     printf("Failure running GetMethodName: %s\n", err);
+    jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(err));
     return nullptr;
   }
 
@@ -72,6 +73,7 @@
     char* err;
     jvmti_env->GetErrorName(result2, &err);
     printf("Failure running GetMethodName(null, null, null): %s\n", err);
+    jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(err));
     return nullptr;
   }
 
@@ -88,6 +90,7 @@
     char* err;
     jvmti_env->GetErrorName(result, &err);
     printf("Failure running GetMethodDeclaringClass: %s\n", err);
+    jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(err));
     return nullptr;
   }
 
@@ -104,12 +107,87 @@
     char* err;
     jvmti_env->GetErrorName(result, &err);
     printf("Failure running GetMethodModifiers: %s\n", err);
+    jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(err));
     return 0;
   }
 
   return modifiers;
 }
 
+static bool ErrorToException(JNIEnv* env, jvmtiError error) {
+  if (error == JVMTI_ERROR_NONE) {
+    return false;
+  }
+
+  ScopedLocalRef<jclass> rt_exception(env, env->FindClass("java/lang/RuntimeException"));
+  if (rt_exception.get() == nullptr) {
+    // CNFE should be pending.
+    return true;
+  }
+
+  char* err;
+  jvmti_env->GetErrorName(error, &err);
+
+  env->ThrowNew(rt_exception.get(), err);
+
+  jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(err));
+  return true;
+}
+
+extern "C" JNIEXPORT jint JNICALL Java_Main_getMaxLocals(
+    JNIEnv* env, jclass klass ATTRIBUTE_UNUSED, jobject method) {
+  jmethodID id = env->FromReflectedMethod(method);
+
+  jint max_locals;
+  jvmtiError result = jvmti_env->GetMaxLocals(id, &max_locals);
+  if (ErrorToException(env, result)) {
+    return -1;
+  }
+
+  return max_locals;
+}
+
+extern "C" JNIEXPORT jint JNICALL Java_Main_getArgumentsSize(
+    JNIEnv* env, jclass klass ATTRIBUTE_UNUSED, jobject method) {
+  jmethodID id = env->FromReflectedMethod(method);
+
+  jint arguments;
+  jvmtiError result = jvmti_env->GetArgumentsSize(id, &arguments);
+  if (ErrorToException(env, result)) {
+    return -1;
+  }
+
+  return arguments;
+}
+
+extern "C" JNIEXPORT jlong JNICALL Java_Main_getMethodLocationStart(
+    JNIEnv* env, jclass klass ATTRIBUTE_UNUSED, jobject method) {
+  jmethodID id = env->FromReflectedMethod(method);
+
+  jlong start;
+  jlong end;
+  jvmtiError result = jvmti_env->GetMethodLocation(id, &start, &end);
+  if (ErrorToException(env, result)) {
+    return -1;
+  }
+
+  return start;
+}
+
+extern "C" JNIEXPORT jlong JNICALL Java_Main_getMethodLocationEnd(
+    JNIEnv* env, jclass klass ATTRIBUTE_UNUSED, jobject method) {
+  jmethodID id = env->FromReflectedMethod(method);
+
+  jlong start;
+  jlong end;
+  jvmtiError result = jvmti_env->GetMethodLocation(id, &start, &end);
+  if (ErrorToException(env, result)) {
+    return -1;
+  }
+
+  return end;
+}
+
 // Don't do anything
 jint OnLoad(JavaVM* vm,
             char* options ATTRIBUTE_UNUSED,
diff --git a/test/910-methods/run b/test/910-methods/run
index 4dd2555..4379349 100755
--- a/test/910-methods/run
+++ b/test/910-methods/run
@@ -14,30 +14,6 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-plugin=libopenjdkjvmtid.so
-agent=libtiagentd.so
-lib=tiagentd
-if  [[ "$@" == *"-O"* ]]; then
-  agent=libtiagent.so
-  plugin=libopenjdkjvmti.so
-  lib=tiagent
-fi
-
-if [[ "$@" == *"--jvm"* ]]; then
-  arg="jvm"
-else
-  arg="art"
-fi
-
-if [[ "$@" != *"--debuggable"* ]]; then
-  other_args=" -Xcompiler-option --debuggable "
-else
-  other_args=""
-fi
-
 ./default-run "$@" --experimental agents \
                    --experimental runtime-plugins \
-                   --runtime-option -agentpath:${agent}=910-methods,${arg} \
-                   --android-runtime-option -Xplugin:${plugin} \
-                   ${other_args} \
-                   --args ${lib}
+                   --jvmti
diff --git a/test/910-methods/src/Main.java b/test/910-methods/src/Main.java
index 3459134..8f722e8 100644
--- a/test/910-methods/src/Main.java
+++ b/test/910-methods/src/Main.java
@@ -68,9 +68,41 @@
       throw new RuntimeException("Modifiers not equal: " + m.getModifiers() + " vs " + modifiers);
     }
     System.out.println(modifiers);
+
+    System.out.print("Max locals: ");
+    try {
+      System.out.println(getMaxLocals(m));
+    } catch (RuntimeException e) {
+      System.out.println(e.getMessage());
+    }
+
+    System.out.print("Argument size: ");
+    try {
+      System.out.println(getArgumentsSize(m));
+    } catch (RuntimeException e) {
+      System.out.println(e.getMessage());
+    }
+
+    System.out.print("Location start: ");
+    try {
+      System.out.println(getMethodLocationStart(m));
+    } catch (RuntimeException e) {
+      System.out.println(e.getMessage());
+    }
+
+    System.out.print("Location end: ");
+    try {
+      System.out.println(getMethodLocationEnd(m));
+    } catch (RuntimeException e) {
+      System.out.println(e.getMessage());
+    }
   }
 
   private static native String[] getMethodName(Method m);
   private static native Class<?> getMethodDeclaringClass(Method m);
   private static native int getMethodModifiers(Method m);
+  private static native int getMaxLocals(Method m);
+  private static native int getArgumentsSize(Method m);
+  private static native long getMethodLocationStart(Method m);
+  private static native long getMethodLocationEnd(Method m);
 }
diff --git a/test/911-get-stack-trace/run b/test/911-get-stack-trace/run
index 43fc325..4379349 100755
--- a/test/911-get-stack-trace/run
+++ b/test/911-get-stack-trace/run
@@ -14,30 +14,6 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-plugin=libopenjdkjvmtid.so
-agent=libtiagentd.so
-lib=tiagentd
-if  [[ "$@" == *"-O"* ]]; then
-  agent=libtiagent.so
-  plugin=libopenjdkjvmti.so
-  lib=tiagent
-fi
-
-if [[ "$@" == *"--jvm"* ]]; then
-  arg="jvm"
-else
-  arg="art"
-fi
-
-if [[ "$@" != *"--debuggable"* ]]; then
-  other_args=" -Xcompiler-option --debuggable "
-else
-  other_args=""
-fi
-
 ./default-run "$@" --experimental agents \
                    --experimental runtime-plugins \
-                   --runtime-option -agentpath:${agent}=911-get-stack-trace,${arg} \
-                   --android-runtime-option -Xplugin:${plugin} \
-                   ${other_args} \
-                   --args ${lib}
+                   --jvmti
diff --git a/test/911-get-stack-trace/stack_trace.cc b/test/911-get-stack-trace/stack_trace.cc
index 57f6a92..b3e8bc3 100644
--- a/test/911-get-stack-trace/stack_trace.cc
+++ b/test/911-get-stack-trace/stack_trace.cc
@@ -63,6 +63,7 @@
       char* err;
       jvmti_env->GetErrorName(result, &err);
       printf("Failure running GetStackTrace: %s\n", err);
+      jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(err));
       return nullptr;
     }
   }
@@ -77,6 +78,7 @@
         char* err;
         jvmti_env->GetErrorName(result2, &err);
         printf("Failure running GetMethodName: %s\n", err);
+        jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(err));
         return nullptr;
       }
     }
@@ -94,6 +96,7 @@
           char* err;
           jvmti_env->GetErrorName(line_result, &err);
           printf("Failure running GetLineNumberTable: %s\n", err);
+          jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(err));
           return nullptr;
         }
         line_number_table = nullptr;
diff --git a/test/912-classes/classes.cc b/test/912-classes/classes.cc
index 838a92a..19d82c5 100644
--- a/test/912-classes/classes.cc
+++ b/test/912-classes/classes.cc
@@ -38,6 +38,7 @@
     char* err;
     jvmti_env->GetErrorName(result, &err);
     printf("Failure running GetClassSignature: %s\n", err);
+    jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(err));
     return nullptr;
   }
 
@@ -61,6 +62,154 @@
   return ret;
 }
 
+extern "C" JNIEXPORT jboolean JNICALL Java_Main_isInterface(
+    JNIEnv* env ATTRIBUTE_UNUSED, jclass Main_klass ATTRIBUTE_UNUSED, jclass klass) {
+  jboolean is_interface = JNI_FALSE;
+  jvmtiError result = jvmti_env->IsInterface(klass, &is_interface);
+  if (result != JVMTI_ERROR_NONE) {
+    char* err;
+    jvmti_env->GetErrorName(result, &err);
+    printf("Failure running IsInterface: %s\n", err);
+    jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(err));
+    return JNI_FALSE;
+  }
+  return is_interface;
+}
+
+extern "C" JNIEXPORT jboolean JNICALL Java_Main_isArrayClass(
+    JNIEnv* env ATTRIBUTE_UNUSED, jclass Main_klass ATTRIBUTE_UNUSED, jclass klass) {
+  jboolean is_array_class = JNI_FALSE;
+  jvmtiError result = jvmti_env->IsArrayClass(klass, &is_array_class);
+  if (result != JVMTI_ERROR_NONE) {
+    char* err;
+    jvmti_env->GetErrorName(result, &err);
+    printf("Failure running IsArrayClass: %s\n", err);
+    jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(err));
+    return JNI_FALSE;
+  }
+  return is_array_class;
+}
+
+extern "C" JNIEXPORT jint JNICALL Java_Main_getClassModifiers(
+    JNIEnv* env ATTRIBUTE_UNUSED, jclass Main_klass ATTRIBUTE_UNUSED, jclass klass) {
+  jint mod;
+  jvmtiError result = jvmti_env->GetClassModifiers(klass, &mod);
+  if (result != JVMTI_ERROR_NONE) {
+    char* err;
+    jvmti_env->GetErrorName(result, &err);
+    printf("Failure running GetClassModifiers: %s\n", err);
+    return JNI_FALSE;
+  }
+  return mod;
+}
+
+extern "C" JNIEXPORT jobjectArray JNICALL Java_Main_getClassFields(
+    JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED, jclass klass) {
+  jint count = 0;
+  jfieldID* fields = nullptr;
+  jvmtiError result = jvmti_env->GetClassFields(klass, &count, &fields);
+  if (result != JVMTI_ERROR_NONE) {
+    char* err;
+    jvmti_env->GetErrorName(result, &err);
+    printf("Failure running GetClassFields: %s\n", err);
+    jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(err));
+    return nullptr;
+  }
+
+  auto callback = [&](jint i) {
+    jint modifiers;
+    // Ignore any errors for simplicity.
+    jvmti_env->GetFieldModifiers(klass, fields[i], &modifiers);
+    constexpr jint kStatic = 0x8;
+    return env->ToReflectedField(klass,
+                                 fields[i],
+                                 (modifiers & kStatic) != 0 ? JNI_TRUE : JNI_FALSE);
+  };
+  jobjectArray ret = CreateObjectArray(env, count, "java/lang/Object", callback);
+  if (fields != nullptr) {
+    jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(fields));
+  }
+  return ret;
+}
+
+extern "C" JNIEXPORT jobjectArray JNICALL Java_Main_getClassMethods(
+    JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED, jclass klass) {
+  jint count = 0;
+  jmethodID* methods = nullptr;
+  jvmtiError result = jvmti_env->GetClassMethods(klass, &count, &methods);
+  if (result != JVMTI_ERROR_NONE) {
+    char* err;
+    jvmti_env->GetErrorName(result, &err);
+    printf("Failure running GetClassMethods: %s\n", err);
+    return nullptr;
+  }
+
+  auto callback = [&](jint i) {
+    jint modifiers;
+    // Ignore any errors for simplicity.
+    jvmti_env->GetMethodModifiers(methods[i], &modifiers);
+    constexpr jint kStatic = 0x8;
+    return env->ToReflectedMethod(klass,
+                                  methods[i],
+                                  (modifiers & kStatic) != 0 ? JNI_TRUE : JNI_FALSE);
+  };
+  jobjectArray ret = CreateObjectArray(env, count, "java/lang/Object", callback);
+  if (methods != nullptr) {
+    jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(methods));
+  }
+  return ret;
+}
+
+extern "C" JNIEXPORT jobjectArray JNICALL Java_Main_getImplementedInterfaces(
+    JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED, jclass klass) {
+  jint count = 0;
+  jclass* classes = nullptr;
+  jvmtiError result = jvmti_env->GetImplementedInterfaces(klass, &count, &classes);
+  if (result != JVMTI_ERROR_NONE) {
+    char* err;
+    jvmti_env->GetErrorName(result, &err);
+    printf("Failure running GetImplementedInterfaces: %s\n", err);
+    return nullptr;
+  }
+
+  auto callback = [&](jint i) {
+    return classes[i];
+  };
+  jobjectArray ret = CreateObjectArray(env, count, "java/lang/Class", callback);
+  if (classes != nullptr) {
+    jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(classes));
+  }
+  return ret;
+}
+
+extern "C" JNIEXPORT jint JNICALL Java_Main_getClassStatus(
+    JNIEnv* env ATTRIBUTE_UNUSED, jclass Main_klass ATTRIBUTE_UNUSED, jclass klass) {
+  jint status;
+  jvmtiError result = jvmti_env->GetClassStatus(klass, &status);
+  if (result != JVMTI_ERROR_NONE) {
+    char* err;
+    jvmti_env->GetErrorName(result, &err);
+    printf("Failure running GetClassStatus: %s\n", err);
+    jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(err));
+    return JNI_FALSE;
+  }
+  return status;
+}
+
+extern "C" JNIEXPORT jobject JNICALL Java_Main_getClassLoader(
+    JNIEnv* env ATTRIBUTE_UNUSED, jclass Main_klass ATTRIBUTE_UNUSED, jclass klass) {
+  jobject classloader;
+  jvmtiError result = jvmti_env->GetClassLoader(klass, &classloader);
+  if (result != JVMTI_ERROR_NONE) {
+    char* err;
+    jvmti_env->GetErrorName(result, &err);
+    printf("Failure running GetClassLoader: %s\n", err);
+    jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(err));
+    return nullptr;
+  }
+  return classloader;
+}
+
 // Don't do anything
 jint OnLoad(JavaVM* vm,
             char* options ATTRIBUTE_UNUSED,
diff --git a/test/912-classes/expected.txt b/test/912-classes/expected.txt
index 71b22f4..3507a1a 100644
--- a/test/912-classes/expected.txt
+++ b/test/912-classes/expected.txt
@@ -1,7 +1,45 @@
 [Ljava/lang/Object;, null]
+1
 [Ljava/lang/String;, null]
+11
 [Ljava/lang/Math;, null]
+11
 [Ljava/util/List;, null]
+601
 [L$Proxy0;, null]
+11
 [I, null]
+411
 [[D, null]
+411
+int interface=false array=false
+$Proxy0 interface=false array=false
+java.lang.Runnable interface=true array=false
+java.lang.String interface=false array=false
+[I interface=false array=true
+[Ljava.lang.Runnable; interface=false array=true
+[Ljava.lang.String; interface=false array=true
+[public static final int java.lang.Integer.BYTES, static final char[] java.lang.Integer.DigitOnes, static final char[] java.lang.Integer.DigitTens, public static final int java.lang.Integer.MAX_VALUE, public static final int java.lang.Integer.MIN_VALUE, public static final int java.lang.Integer.SIZE, private static final java.lang.String[] java.lang.Integer.SMALL_NEG_VALUES, private static final java.lang.String[] java.lang.Integer.SMALL_NONNEG_VALUES, public static final java.lang.Class java.lang.Integer.TYPE, static final char[] java.lang.Integer.digits, private static final long java.lang.Integer.serialVersionUID, static final int[] java.lang.Integer.sizeTable, private final int java.lang.Integer.value]
+[]
+[]
+[java.lang.Integer(), public java.lang.Integer(int), public java.lang.Integer(java.lang.String) throws java.lang.NumberFormatException, public static int java.lang.Integer.bitCount(int), public static int java.lang.Integer.compare(int,int), public static int java.lang.Integer.compareUnsigned(int,int), public static java.lang.Integer java.lang.Integer.decode(java.lang.String) throws java.lang.NumberFormatException, public static int java.lang.Integer.divideUnsigned(int,int), static int java.lang.Integer.formatUnsignedInt(int,int,char[],int,int), static void java.lang.Integer.getChars(int,int,char[]), public static java.lang.Integer java.lang.Integer.getInteger(java.lang.String), public static java.lang.Integer java.lang.Integer.getInteger(java.lang.String,int), public static java.lang.Integer java.lang.Integer.getInteger(java.lang.String,java.lang.Integer), public static int java.lang.Integer.hashCode(int), public static int java.lang.Integer.highestOneBit(int), public static int java.lang.Integer.lowestOneBit(int), public static int java.lang.Integer.max(int,int), public static int java.lang.Integer.min(int,int), public static int java.lang.Integer.numberOfLeadingZeros(int), public static int java.lang.Integer.numberOfTrailingZeros(int), public static int java.lang.Integer.parseInt(java.lang.String) throws java.lang.NumberFormatException, public static int java.lang.Integer.parseInt(java.lang.String,int) throws java.lang.NumberFormatException, public static int java.lang.Integer.parseUnsignedInt(java.lang.String) throws java.lang.NumberFormatException, public static int java.lang.Integer.parseUnsignedInt(java.lang.String,int) throws java.lang.NumberFormatException, public static int java.lang.Integer.remainderUnsigned(int,int), public static int java.lang.Integer.reverse(int), public static int java.lang.Integer.reverseBytes(int), public static int java.lang.Integer.rotateLeft(int,int), public static int java.lang.Integer.rotateRight(int,int), public static int java.lang.Integer.signum(int), static int java.lang.Integer.stringSize(int), public static int java.lang.Integer.sum(int,int), public static java.lang.String java.lang.Integer.toBinaryString(int), public static java.lang.String java.lang.Integer.toHexString(int), public static java.lang.String java.lang.Integer.toOctalString(int), public static java.lang.String java.lang.Integer.toString(int), public static java.lang.String java.lang.Integer.toString(int,int), public static long java.lang.Integer.toUnsignedLong(int), public static java.lang.String java.lang.Integer.toUnsignedString(int), public static java.lang.String java.lang.Integer.toUnsignedString(int,int), private static java.lang.String java.lang.Integer.toUnsignedString0(int,int), public static java.lang.Integer java.lang.Integer.valueOf(int), public static java.lang.Integer java.lang.Integer.valueOf(java.lang.String) throws java.lang.NumberFormatException, public static java.lang.Integer java.lang.Integer.valueOf(java.lang.String,int) throws java.lang.NumberFormatException, public byte java.lang.Integer.byteValue(), public int java.lang.Integer.compareTo(java.lang.Integer), public int java.lang.Integer.compareTo(java.lang.Object), public double java.lang.Integer.doubleValue(), public boolean java.lang.Integer.equals(java.lang.Object), public float java.lang.Integer.floatValue(), public int java.lang.Integer.hashCode(), public int java.lang.Integer.intValue(), public long java.lang.Integer.longValue(), public short java.lang.Integer.shortValue(), public java.lang.String java.lang.Integer.toString()]
+[]
+[]
+int 100000
+class [Ljava.lang.String; 10000
+class java.lang.Object 111
+class Main$TestForNonInit 11
+class Main$TestForInitFail 1001
+int []
+class [Ljava.lang.String; []
+class java.lang.Object []
+interface Main$InfA []
+interface Main$InfB [interface Main$InfA]
+interface Main$InfC [interface Main$InfB]
+class Main$ClassA [interface Main$InfA]
+class Main$ClassB [interface Main$InfB]
+class Main$ClassC [interface Main$InfA, interface Main$InfC]
+class java.lang.String null
+class [Ljava.lang.String; null
+interface Main$InfA dalvik.system.PathClassLoader
+class $Proxy0 dalvik.system.PathClassLoader
diff --git a/test/912-classes/run b/test/912-classes/run
index 64bbb98..4379349 100755
--- a/test/912-classes/run
+++ b/test/912-classes/run
@@ -14,30 +14,6 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-plugin=libopenjdkjvmtid.so
-agent=libtiagentd.so
-lib=tiagentd
-if  [[ "$@" == *"-O"* ]]; then
-  agent=libtiagent.so
-  plugin=libopenjdkjvmti.so
-  lib=tiagent
-fi
-
-if [[ "$@" == *"--jvm"* ]]; then
-  arg="jvm"
-else
-  arg="art"
-fi
-
-if [[ "$@" != *"--debuggable"* ]]; then
-  other_args=" -Xcompiler-option --debuggable "
-else
-  other_args=""
-fi
-
 ./default-run "$@" --experimental agents \
                    --experimental runtime-plugins \
-                   --runtime-option -agentpath:${agent}=912-classes,${arg} \
-                   --android-runtime-option -Xplugin:${plugin} \
-                   ${other_args} \
-                   --args ${lib}
+                   --jvmti
diff --git a/test/912-classes/src/Main.java b/test/912-classes/src/Main.java
index 025584e..69e5a4c 100644
--- a/test/912-classes/src/Main.java
+++ b/test/912-classes/src/Main.java
@@ -34,6 +34,48 @@
 
     testClass(int.class);
     testClass(double[].class);
+
+    testClassType(int.class);
+    testClassType(getProxyClass());
+    testClassType(Runnable.class);
+    testClassType(String.class);
+
+    testClassType(int[].class);
+    testClassType(Runnable[].class);
+    testClassType(String[].class);
+
+    testClassFields(Integer.class);
+    testClassFields(int.class);
+    testClassFields(String[].class);
+
+    testClassMethods(Integer.class);
+    testClassMethods(int.class);
+    testClassMethods(String[].class);
+
+    testClassStatus(int.class);
+    testClassStatus(String[].class);
+    testClassStatus(Object.class);
+    testClassStatus(TestForNonInit.class);
+    try {
+      System.out.println(TestForInitFail.dummy);
+    } catch (ExceptionInInitializerError e) {
+    }
+    testClassStatus(TestForInitFail.class);
+
+    testInterfaces(int.class);
+    testInterfaces(String[].class);
+    testInterfaces(Object.class);
+    testInterfaces(InfA.class);
+    testInterfaces(InfB.class);
+    testInterfaces(InfC.class);
+    testInterfaces(ClassA.class);
+    testInterfaces(ClassB.class);
+    testInterfaces(ClassC.class);
+
+    testClassLoader(String.class);
+    testClassLoader(String[].class);
+    testClassLoader(InfA.class);
+    testClassLoader(getProxyClass());
   }
 
   private static Class<?> proxyClass = null;
@@ -55,7 +97,92 @@
   private static void testClass(Class<?> base) throws Exception {
     String[] result = getClassSignature(base);
     System.out.println(Arrays.toString(result));
+    int mod = getClassModifiers(base);
+    if (mod != base.getModifiers()) {
+      throw new RuntimeException("Unexpected modifiers: " + base.getModifiers() + " vs " + mod);
+    }
+    System.out.println(Integer.toHexString(mod));
+  }
+
+  private static void testClassType(Class<?> c) throws Exception {
+    boolean isInterface = isInterface(c);
+    boolean isArray = isArrayClass(c);
+    System.out.println(c.getName() + " interface=" + isInterface + " array=" + isArray);
+  }
+
+  private static void testClassFields(Class<?> c) throws Exception {
+    System.out.println(Arrays.toString(getClassFields(c)));
+  }
+
+  private static void testClassMethods(Class<?> c) throws Exception {
+    System.out.println(Arrays.toString(getClassMethods(c)));
+  }
+
+  private static void testClassStatus(Class<?> c) {
+    System.out.println(c + " " + Integer.toBinaryString(getClassStatus(c)));
+  }
+
+  private static void testInterfaces(Class<?> c) {
+    System.out.println(c + " " + Arrays.toString(getImplementedInterfaces(c)));
+  }
+
+  private static boolean IsBootClassLoader(ClassLoader l) {
+    // Hacky check for Android's fake boot classloader.
+    return l.getClass().getName().equals("java.lang.BootClassLoader");
+  }
+
+  private static void testClassLoader(Class<?> c) {
+    Object cl = getClassLoader(c);
+    System.out.println(c + " " + (cl != null ? cl.getClass().getName() : "null"));
+    if (cl == null) {
+      if (c.getClassLoader() != null && !IsBootClassLoader(c.getClassLoader())) {
+        throw new RuntimeException("Expected " + c.getClassLoader() + ", but got null.");
+      }
+    } else {
+      if (!(cl instanceof ClassLoader)) {
+        throw new RuntimeException("Unexpected \"classloader\": " + cl + " (" + cl.getClass() +
+            ")");
+      }
+      if (cl != c.getClassLoader()) {
+        throw new RuntimeException("Unexpected classloader: " + c.getClassLoader() + " vs " + cl);
+      }
+    }
   }
 
   private static native String[] getClassSignature(Class<?> c);
+
+  private static native boolean isInterface(Class<?> c);
+  private static native boolean isArrayClass(Class<?> c);
+
+  private static native int getClassModifiers(Class<?> c);
+
+  private static native Object[] getClassFields(Class<?> c);
+  private static native Object[] getClassMethods(Class<?> c);
+  private static native Class[] getImplementedInterfaces(Class<?> c);
+
+  private static native int getClassStatus(Class<?> c);
+
+  private static native Object getClassLoader(Class<?> c);
+
+  private static class TestForNonInit {
+    public static double dummy = Math.random();  // So it can't be compile-time initialized.
+  }
+
+  private static class TestForInitFail {
+    public static int dummy = ((int)Math.random())/0;  // So it throws when initializing.
+  }
+
+  public static interface InfA {
+  }
+  public static interface InfB extends InfA {
+  }
+  public static interface InfC extends InfB {
+  }
+
+  public abstract static class ClassA implements InfA {
+  }
+  public abstract static class ClassB extends ClassA implements InfB {
+  }
+  public abstract static class ClassC implements InfA, InfC {
+  }
 }
diff --git a/test/913-heaps/heaps.cc b/test/913-heaps/heaps.cc
index 49ab7dd..0b232af 100644
--- a/test/913-heaps/heaps.cc
+++ b/test/913-heaps/heaps.cc
@@ -49,6 +49,7 @@
     char* err;
     jvmti_env->GetErrorName(ret, &err);
     printf("Error forcing a garbage collection: %s\n", err);
+    jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(err));
   }
 }
 
@@ -106,6 +107,7 @@
     char* err;
     jvmti_env->GetErrorName(ret, &err);
     printf("Failure running FollowReferences: %s\n", err);
+    jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(err));
     return false;
   }
   return true;
diff --git a/test/913-heaps/run b/test/913-heaps/run
index 7bd8cbd..4379349 100755
--- a/test/913-heaps/run
+++ b/test/913-heaps/run
@@ -14,30 +14,6 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-plugin=libopenjdkjvmtid.so
-agent=libtiagentd.so
-lib=tiagentd
-if  [[ "$@" == *"-O"* ]]; then
-  agent=libtiagent.so
-  plugin=libopenjdkjvmti.so
-  lib=tiagent
-fi
-
-if [[ "$@" == *"--jvm"* ]]; then
-  arg="jvm"
-else
-  arg="art"
-fi
-
-if [[ "$@" != *"--debuggable"* ]]; then
-  other_args=" -Xcompiler-option --debuggable "
-else
-  other_args=""
-fi
-
 ./default-run "$@" --experimental agents \
                    --experimental runtime-plugins \
-                   --runtime-option -agentpath:${agent}=913-heaps,${arg} \
-                   --android-runtime-option -Xplugin:${plugin} \
-                   ${other_args} \
-                   --args ${lib}
+                   --jvmti
diff --git a/test/914-hello-obsolescence/build b/test/914-hello-obsolescence/build
new file mode 100755
index 0000000..898e2e5
--- /dev/null
+++ b/test/914-hello-obsolescence/build
@@ -0,0 +1,17 @@
+#!/bin/bash
+#
+# Copyright 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+./default-build "$@" --experimental agents
diff --git a/test/914-hello-obsolescence/expected.txt b/test/914-hello-obsolescence/expected.txt
new file mode 100644
index 0000000..83efda1
--- /dev/null
+++ b/test/914-hello-obsolescence/expected.txt
@@ -0,0 +1,9 @@
+hello
+Not doing anything here
+goodbye
+hello
+transforming calling function
+goodbye
+Hello - Transformed
+Not doing anything here
+Goodbye - Transformed
diff --git a/test/914-hello-obsolescence/info.txt b/test/914-hello-obsolescence/info.txt
new file mode 100644
index 0000000..c8b892c
--- /dev/null
+++ b/test/914-hello-obsolescence/info.txt
@@ -0,0 +1 @@
+Tests basic obsolete method support
diff --git a/test/914-hello-obsolescence/run b/test/914-hello-obsolescence/run
new file mode 100755
index 0000000..4379349
--- /dev/null
+++ b/test/914-hello-obsolescence/run
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+./default-run "$@" --experimental agents \
+                   --experimental runtime-plugins \
+                   --jvmti
diff --git a/test/914-hello-obsolescence/src/Main.java b/test/914-hello-obsolescence/src/Main.java
new file mode 100644
index 0000000..46266ef
--- /dev/null
+++ b/test/914-hello-obsolescence/src/Main.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.util.Base64;
+
+public class Main {
+  // class Transform {
+  //   public void sayHi(Runnable r) {
+  //     System.out.println("Hello - Transformed");
+  //     r.run();
+  //     System.out.println("Goodbye - Transformed");
+  //   }
+  // }
+  private static final byte[] CLASS_BYTES = Base64.getDecoder().decode(
+    "yv66vgAAADQAJAoACAARCQASABMIABQKABUAFgsAFwAYCAAZBwAaBwAbAQAGPGluaXQ+AQADKClW" +
+    "AQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEABXNheUhpAQAXKExqYXZhL2xhbmcvUnVubmFibGU7" +
+    "KVYBAApTb3VyY2VGaWxlAQAOVHJhbnNmb3JtLmphdmEMAAkACgcAHAwAHQAeAQATSGVsbG8gLSBU" +
+    "cmFuc2Zvcm1lZAcAHwwAIAAhBwAiDAAjAAoBABVHb29kYnllIC0gVHJhbnNmb3JtZWQBAAlUcmFu" +
+    "c2Zvcm0BABBqYXZhL2xhbmcvT2JqZWN0AQAQamF2YS9sYW5nL1N5c3RlbQEAA291dAEAFUxqYXZh" +
+    "L2lvL1ByaW50U3RyZWFtOwEAE2phdmEvaW8vUHJpbnRTdHJlYW0BAAdwcmludGxuAQAVKExqYXZh" +
+    "L2xhbmcvU3RyaW5nOylWAQASamF2YS9sYW5nL1J1bm5hYmxlAQADcnVuACAABwAIAAAAAAACAAAA" +
+    "CQAKAAEACwAAAB0AAQABAAAABSq3AAGxAAAAAQAMAAAABgABAAAAAQABAA0ADgABAAsAAAA7AAIA" +
+    "AgAAABeyAAISA7YABCu5AAUBALIAAhIGtgAEsQAAAAEADAAAABIABAAAAAMACAAEAA4ABQAWAAYA" +
+    "AQAPAAAAAgAQ");
+  private static final byte[] DEX_BYTES = Base64.getDecoder().decode(
+    "ZGV4CjAzNQAYeAMMXgYWxoeSHAS9EWKCCtVRSAGpqZVQAwAAcAAAAHhWNBIAAAAAAAAAALACAAAR" +
+    "AAAAcAAAAAcAAAC0AAAAAwAAANAAAAABAAAA9AAAAAUAAAD8AAAAAQAAACQBAAAMAgAARAEAAKIB" +
+    "AACqAQAAwQEAANYBAADjAQAA+gEAAA4CAAAkAgAAOAIAAEwCAABcAgAAXwIAAGMCAAB3AgAAfAIA" +
+    "AIUCAACKAgAAAwAAAAQAAAAFAAAABgAAAAcAAAAIAAAACgAAAAoAAAAGAAAAAAAAAAsAAAAGAAAA" +
+    "lAEAAAsAAAAGAAAAnAEAAAUAAQANAAAAAAAAAAAAAAAAAAEAEAAAAAEAAgAOAAAAAgAAAAAAAAAD" +
+    "AAAADwAAAAAAAAAAAAAAAgAAAAAAAAAJAAAAAAAAAJ8CAAAAAAAAAQABAAEAAACRAgAABAAAAHAQ" +
+    "AwAAAA4ABAACAAIAAACWAgAAFAAAAGIAAAAbAQIAAABuIAIAEAByEAQAAwBiAAAAGwEBAAAAbiAC" +
+    "ABAADgABAAAAAwAAAAEAAAAEAAY8aW5pdD4AFUdvb2RieWUgLSBUcmFuc2Zvcm1lZAATSGVsbG8g" +
+    "LSBUcmFuc2Zvcm1lZAALTFRyYW5zZm9ybTsAFUxqYXZhL2lvL1ByaW50U3RyZWFtOwASTGphdmEv" +
+    "bGFuZy9PYmplY3Q7ABRMamF2YS9sYW5nL1J1bm5hYmxlOwASTGphdmEvbGFuZy9TdHJpbmc7ABJM" +
+    "amF2YS9sYW5nL1N5c3RlbTsADlRyYW5zZm9ybS5qYXZhAAFWAAJWTAASZW1pdHRlcjogamFjay00" +
+    "LjEzAANvdXQAB3ByaW50bG4AA3J1bgAFc2F5SGkAAQAHDgADAQAHDoc8hwAAAAEBAICABMQCAQHc" +
+    "AgAAAA0AAAAAAAAAAQAAAAAAAAABAAAAEQAAAHAAAAACAAAABwAAALQAAAADAAAAAwAAANAAAAAE" +
+    "AAAAAQAAAPQAAAAFAAAABQAAAPwAAAAGAAAAAQAAACQBAAABIAAAAgAAAEQBAAABEAAAAgAAAJQB" +
+    "AAACIAAAEQAAAKIBAAADIAAAAgAAAJECAAAAIAAAAQAAAJ8CAAAAEAAAAQAAALACAAA=");
+
+  public static void main(String[] args) {
+    System.loadLibrary(args[1]);
+    doTest(new Transform());
+  }
+
+  public static void doTest(Transform t) {
+    t.sayHi(() -> { System.out.println("Not doing anything here"); });
+    t.sayHi(() -> {
+      System.out.println("transforming calling function");
+      doCommonClassRedefinition(Transform.class, CLASS_BYTES, DEX_BYTES);
+    });
+    t.sayHi(() -> { System.out.println("Not doing anything here"); });
+  }
+
+  // Transforms the class
+  private static native void doCommonClassRedefinition(Class<?> target,
+                                                       byte[] classfile,
+                                                       byte[] dexfile);
+}
diff --git a/test/914-hello-obsolescence/src/Transform.java b/test/914-hello-obsolescence/src/Transform.java
new file mode 100644
index 0000000..8cda6cd
--- /dev/null
+++ b/test/914-hello-obsolescence/src/Transform.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+class Transform {
+  public void sayHi(Runnable r) {
+    // Use lower 'h' to make sure the string will have a different string id
+    // than the transformation (the transformation code is the same except
+    // the actual printed String, which was making the test inacurately passing
+    // in JIT mode when loading the string from the dex cache, as the string ids
+    // of the two different strings were the same).
+    // We know the string ids will be different because lexicographically:
+    // "Hello" < "LTransform;" < "hello".
+    System.out.println("hello");
+    r.run();
+    System.out.println("goodbye");
+  }
+}
diff --git a/test/915-obsolete-2/build b/test/915-obsolete-2/build
new file mode 100755
index 0000000..898e2e5
--- /dev/null
+++ b/test/915-obsolete-2/build
@@ -0,0 +1,17 @@
+#!/bin/bash
+#
+# Copyright 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+./default-build "$@" --experimental agents
diff --git a/test/915-obsolete-2/expected.txt b/test/915-obsolete-2/expected.txt
new file mode 100644
index 0000000..04aff3a
--- /dev/null
+++ b/test/915-obsolete-2/expected.txt
@@ -0,0 +1,21 @@
+Pre Start private method call
+hello - private
+Post Start private method call
+Not doing anything here
+Pre Finish private method call
+goodbye - private
+Post Finish private method call
+Pre Start private method call
+hello - private
+Post Start private method call
+transforming calling function
+Pre Finish private method call
+Goodbye - private - Transformed
+Post Finish private method call
+Pre Start private method call - Transformed
+Hello - private - Transformed
+Post Start private method call - Transformed
+Not doing anything here
+Pre Finish private method call - Transformed
+Goodbye - private - Transformed
+Post Finish private method call - Transformed
diff --git a/test/915-obsolete-2/info.txt b/test/915-obsolete-2/info.txt
new file mode 100644
index 0000000..c8b892c
--- /dev/null
+++ b/test/915-obsolete-2/info.txt
@@ -0,0 +1 @@
+Tests basic obsolete method support
diff --git a/test/915-obsolete-2/run b/test/915-obsolete-2/run
new file mode 100755
index 0000000..4379349
--- /dev/null
+++ b/test/915-obsolete-2/run
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+./default-run "$@" --experimental agents \
+                   --experimental runtime-plugins \
+                   --jvmti
diff --git a/test/915-obsolete-2/src/Main.java b/test/915-obsolete-2/src/Main.java
new file mode 100644
index 0000000..bbeb726
--- /dev/null
+++ b/test/915-obsolete-2/src/Main.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.util.Base64;
+
+public class Main {
+  // class Transform {
+  //   private void Start() {
+  //     System.out.println("Hello - private - Transformed");
+  //   }
+  //
+  //   private void Finish() {
+  //     System.out.println("Goodbye - private - Transformed");
+  //   }
+  //
+  //   public void sayHi(Runnable r) {
+  //     System.out.println("Pre Start private method call - Transformed");
+  //     Start();
+  //     System.out.println("Post Start private method call - Transformed");
+  //     r.run();
+  //     System.out.println("Pre Finish private method call - Transformed");
+  //     Finish();
+  //     System.out.println("Post Finish private method call - Transformed");
+  //   }
+  // }
+  private static final byte[] CLASS_BYTES = Base64.getDecoder().decode(
+    "yv66vgAAADQAMgoADgAZCQAaABsIABwKAB0AHggAHwgAIAoADQAhCAAiCwAjACQIACUKAA0AJggA" +
+    "JwcAKAcAKQEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAAVTdGFydAEA" +
+    "BkZpbmlzaAEABXNheUhpAQAXKExqYXZhL2xhbmcvUnVubmFibGU7KVYBAApTb3VyY2VGaWxlAQAO" +
+    "VHJhbnNmb3JtLmphdmEMAA8AEAcAKgwAKwAsAQAdSGVsbG8gLSBwcml2YXRlIC0gVHJhbnNmb3Jt" +
+    "ZWQHAC0MAC4ALwEAH0dvb2RieWUgLSBwcml2YXRlIC0gVHJhbnNmb3JtZWQBACtQcmUgU3RhcnQg" +
+    "cHJpdmF0ZSBtZXRob2QgY2FsbCAtIFRyYW5zZm9ybWVkDAATABABACxQb3N0IFN0YXJ0IHByaXZh" +
+    "dGUgbWV0aG9kIGNhbGwgLSBUcmFuc2Zvcm1lZAcAMAwAMQAQAQAsUHJlIEZpbmlzaCBwcml2YXRl" +
+    "IG1ldGhvZCBjYWxsIC0gVHJhbnNmb3JtZWQMABQAEAEALVBvc3QgRmluaXNoIHByaXZhdGUgbWV0" +
+    "aG9kIGNhbGwgLSBUcmFuc2Zvcm1lZAEACVRyYW5zZm9ybQEAEGphdmEvbGFuZy9PYmplY3QBABBq" +
+    "YXZhL2xhbmcvU3lzdGVtAQADb3V0AQAVTGphdmEvaW8vUHJpbnRTdHJlYW07AQATamF2YS9pby9Q" +
+    "cmludFN0cmVhbQEAB3ByaW50bG4BABUoTGphdmEvbGFuZy9TdHJpbmc7KVYBABJqYXZhL2xhbmcv" +
+    "UnVubmFibGUBAANydW4AIAANAA4AAAAAAAQAAAAPABAAAQARAAAAHQABAAEAAAAFKrcAAbEAAAAB" +
+    "ABIAAAAGAAEAAAABAAIAEwAQAAEAEQAAACUAAgABAAAACbIAAhIDtgAEsQAAAAEAEgAAAAoAAgAA" +
+    "AAMACAAEAAIAFAAQAAEAEQAAACUAAgABAAAACbIAAhIFtgAEsQAAAAEAEgAAAAoAAgAAAAcACAAI" +
+    "AAEAFQAWAAEAEQAAAGMAAgACAAAAL7IAAhIGtgAEKrcAB7IAAhIItgAEK7kACQEAsgACEgq2AAQq" +
+    "twALsgACEgy2AASxAAAAAQASAAAAIgAIAAAACwAIAAwADAANABQADgAaAA8AIgAQACYAEQAuABIA" +
+    "AQAXAAAAAgAY");
+  private static final byte[] DEX_BYTES = Base64.getDecoder().decode(
+    "ZGV4CjAzNQCM0QYTJmX+NsZXkImojgSkJtXyuew3oaXcBAAAcAAAAHhWNBIAAAAAAAAAADwEAAAX" +
+    "AAAAcAAAAAcAAADMAAAAAwAAAOgAAAABAAAADAEAAAcAAAAUAQAAAQAAAEwBAABwAwAAbAEAAD4C" +
+    "AABGAgAATgIAAG8CAACOAgAAmwIAALICAADGAgAA3AIAAPACAAAEAwAAMwMAAGEDAACPAwAAvAMA" +
+    "AMMDAADTAwAA1gMAANoDAADuAwAA8wMAAPwDAAABBAAABAAAAAUAAAAGAAAABwAAAAgAAAAJAAAA" +
+    "EAAAABAAAAAGAAAAAAAAABEAAAAGAAAAMAIAABEAAAAGAAAAOAIAAAUAAQATAAAAAAAAAAAAAAAA" +
+    "AAAAAQAAAAAAAAAOAAAAAAABABYAAAABAAIAFAAAAAIAAAAAAAAAAwAAABUAAAAAAAAAAAAAAAIA" +
+    "AAAAAAAADwAAAAAAAAAmBAAAAAAAAAEAAQABAAAACAQAAAQAAABwEAUAAAAOAAMAAQACAAAADQQA" +
+    "AAkAAABiAAAAGwECAAAAbiAEABAADgAAAAMAAQACAAAAEwQAAAkAAABiAAAAGwEDAAAAbiAEABAA" +
+    "DgAAAAQAAgACAAAAGQQAACoAAABiAAAAGwENAAAAbiAEABAAcBACAAIAYgAAABsBCwAAAG4gBAAQ" +
+    "AHIQBgADAGIAAAAbAQwAAABuIAQAEABwEAEAAgBiAAAAGwEKAAAAbiAEABAADgABAAAAAwAAAAEA" +
+    "AAAEAAY8aW5pdD4ABkZpbmlzaAAfR29vZGJ5ZSAtIHByaXZhdGUgLSBUcmFuc2Zvcm1lZAAdSGVs" +
+    "bG8gLSBwcml2YXRlIC0gVHJhbnNmb3JtZWQAC0xUcmFuc2Zvcm07ABVMamF2YS9pby9QcmludFN0" +
+    "cmVhbTsAEkxqYXZhL2xhbmcvT2JqZWN0OwAUTGphdmEvbGFuZy9SdW5uYWJsZTsAEkxqYXZhL2xh" +
+    "bmcvU3RyaW5nOwASTGphdmEvbGFuZy9TeXN0ZW07AC1Qb3N0IEZpbmlzaCBwcml2YXRlIG1ldGhv" +
+    "ZCBjYWxsIC0gVHJhbnNmb3JtZWQALFBvc3QgU3RhcnQgcHJpdmF0ZSBtZXRob2QgY2FsbCAtIFRy" +
+    "YW5zZm9ybWVkACxQcmUgRmluaXNoIHByaXZhdGUgbWV0aG9kIGNhbGwgLSBUcmFuc2Zvcm1lZAAr" +
+    "UHJlIFN0YXJ0IHByaXZhdGUgbWV0aG9kIGNhbGwgLSBUcmFuc2Zvcm1lZAAFU3RhcnQADlRyYW5z" +
+    "Zm9ybS5qYXZhAAFWAAJWTAASZW1pdHRlcjogamFjay00LjEzAANvdXQAB3ByaW50bG4AA3J1bgAF" +
+    "c2F5SGkAAQAHDgAHAAcOhwADAAcOhwALAQAHDoc8hzyHPIcAAAADAQCAgATsAgEChAMBAqgDAwHM" +
+    "Aw0AAAAAAAAAAQAAAAAAAAABAAAAFwAAAHAAAAACAAAABwAAAMwAAAADAAAAAwAAAOgAAAAEAAAA" +
+    "AQAAAAwBAAAFAAAABwAAABQBAAAGAAAAAQAAAEwBAAABIAAABAAAAGwBAAABEAAAAgAAADACAAAC" +
+    "IAAAFwAAAD4CAAADIAAABAAAAAgEAAAAIAAAAQAAACYEAAAAEAAAAQAAADwEAAA=");
+
+  public static void main(String[] args) {
+    System.loadLibrary(args[1]);
+    doTest(new Transform());
+  }
+
+  public static void doTest(Transform t) {
+    t.sayHi(() -> { System.out.println("Not doing anything here"); });
+    t.sayHi(() -> {
+      System.out.println("transforming calling function");
+      doCommonClassRedefinition(Transform.class, CLASS_BYTES, DEX_BYTES);
+    });
+    t.sayHi(() -> { System.out.println("Not doing anything here"); });
+  }
+
+  // Transforms the class
+  private static native void doCommonClassRedefinition(Class<?> target,
+                                                       byte[] classfile,
+                                                       byte[] dexfile);
+}
diff --git a/test/915-obsolete-2/src/Transform.java b/test/915-obsolete-2/src/Transform.java
new file mode 100644
index 0000000..e914e29
--- /dev/null
+++ b/test/915-obsolete-2/src/Transform.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+class Transform {
+  private void Start() {
+    System.out.println("hello - private");
+  }
+
+  private void Finish() {
+    System.out.println("goodbye - private");
+  }
+
+  public void sayHi(Runnable r) {
+    System.out.println("Pre Start private method call");
+    Start();
+    System.out.println("Post Start private method call");
+    r.run();
+    System.out.println("Pre Finish private method call");
+    Finish();
+    System.out.println("Post Finish private method call");
+  }
+}
diff --git a/test/916-obsolete-jit/build b/test/916-obsolete-jit/build
new file mode 100755
index 0000000..898e2e5
--- /dev/null
+++ b/test/916-obsolete-jit/build
@@ -0,0 +1,17 @@
+#!/bin/bash
+#
+# Copyright 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+./default-build "$@" --experimental agents
diff --git a/test/916-obsolete-jit/expected.txt b/test/916-obsolete-jit/expected.txt
new file mode 100644
index 0000000..4caefc6
--- /dev/null
+++ b/test/916-obsolete-jit/expected.txt
@@ -0,0 +1,21 @@
+Pre Start private method call
+hello - private
+Post Start private method call
+Not doing anything here
+Pre Finish private method call
+goodbye - private
+Post Finish private method call
+Pre Start private method call
+hello - private
+Post Start private method call
+transforming calling function
+Pre Finish private method call
+Goodbye - private - Transformed
+Post Finish private method call
+pre Start private method call - Transformed
+Hello - private - Transformed
+post Start private method call - Transformed
+Not doing anything here
+pre Finish private method call - Transformed
+Goodbye - private - Transformed
+post Finish private method call - Transformed
diff --git a/test/916-obsolete-jit/info.txt b/test/916-obsolete-jit/info.txt
new file mode 100644
index 0000000..c8b892c
--- /dev/null
+++ b/test/916-obsolete-jit/info.txt
@@ -0,0 +1 @@
+Tests basic obsolete method support
diff --git a/test/916-obsolete-jit/run b/test/916-obsolete-jit/run
new file mode 100755
index 0000000..9056211
--- /dev/null
+++ b/test/916-obsolete-jit/run
@@ -0,0 +1,27 @@
+#!/bin/bash
+#
+# Copyright 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# We are testing the redefinition of compiled code but with jvmti we only allow
+# jitted compiled code so always add the --jit argument.
+if [[ "$@" == *"--jit"* ]]; then
+  other_args=""
+else
+  other_args="--jit"
+fi
+./default-run "$@" --experimental agents \
+                   --experimental runtime-plugins \
+                   ${other_args} \
+                   --jvmti
diff --git a/test/916-obsolete-jit/src/Main.java b/test/916-obsolete-jit/src/Main.java
new file mode 100644
index 0000000..74eb003
--- /dev/null
+++ b/test/916-obsolete-jit/src/Main.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.util.function.Consumer;
+import java.lang.reflect.Method;
+import java.util.Base64;
+
+public class Main {
+
+  // import java.util.function.Consumer;
+  //
+  // class Transform {
+  //   private void Start(Consumer<String> reporter) {
+  //     reporter.accept("Hello - private - Transformed");
+  //   }
+  //
+  //   private void Finish(Consumer<String> reporter) {
+  //     reporter.accept("Goodbye - private - Transformed");
+  //   }
+  //
+  //   public void sayHi(Runnable r, Consumer<String> reporter) {
+  //     reporter.accept("pre Start private method call - Transformed");
+  //     Start(reporter);
+  //     reporter.accept("post Start private method call - Transformed");
+  //     r.run();
+  //     reporter.accept("pre Finish private method call - Transformed");
+  //     Finish(reporter);
+  //     reporter.accept("post Finish private method call - Transformed");
+  //   }
+  // }
+  private static final byte[] CLASS_BYTES = Base64.getDecoder().decode(
+    "yv66vgAAADQAMAoADQAcCAAdCwAeAB8IACAIACEKAAwAIggAIwsAJAAlCAAmCgAMACcIACgHACkH" +
+    "ACoBAAY8aW5pdD4BAAMoKVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQAFU3RhcnQBACAoTGph" +
+    "dmEvdXRpbC9mdW5jdGlvbi9Db25zdW1lcjspVgEACVNpZ25hdHVyZQEANChMamF2YS91dGlsL2Z1" +
+    "bmN0aW9uL0NvbnN1bWVyPExqYXZhL2xhbmcvU3RyaW5nOz47KVYBAAZGaW5pc2gBAAVzYXlIaQEA" +
+    "NChMamF2YS9sYW5nL1J1bm5hYmxlO0xqYXZhL3V0aWwvZnVuY3Rpb24vQ29uc3VtZXI7KVYBAEgo" +
+    "TGphdmEvbGFuZy9SdW5uYWJsZTtMamF2YS91dGlsL2Z1bmN0aW9uL0NvbnN1bWVyPExqYXZhL2xh" +
+    "bmcvU3RyaW5nOz47KVYBAApTb3VyY2VGaWxlAQAOVHJhbnNmb3JtLmphdmEMAA4ADwEAHUhlbGxv" +
+    "IC0gcHJpdmF0ZSAtIFRyYW5zZm9ybWVkBwArDAAsAC0BAB9Hb29kYnllIC0gcHJpdmF0ZSAtIFRy" +
+    "YW5zZm9ybWVkAQArcHJlIFN0YXJ0IHByaXZhdGUgbWV0aG9kIGNhbGwgLSBUcmFuc2Zvcm1lZAwA" +
+    "EgATAQAscG9zdCBTdGFydCBwcml2YXRlIG1ldGhvZCBjYWxsIC0gVHJhbnNmb3JtZWQHAC4MAC8A" +
+    "DwEALHByZSBGaW5pc2ggcHJpdmF0ZSBtZXRob2QgY2FsbCAtIFRyYW5zZm9ybWVkDAAWABMBAC1w" +
+    "b3N0IEZpbmlzaCBwcml2YXRlIG1ldGhvZCBjYWxsIC0gVHJhbnNmb3JtZWQBAAlUcmFuc2Zvcm0B" +
+    "ABBqYXZhL2xhbmcvT2JqZWN0AQAbamF2YS91dGlsL2Z1bmN0aW9uL0NvbnN1bWVyAQAGYWNjZXB0" +
+    "AQAVKExqYXZhL2xhbmcvT2JqZWN0OylWAQASamF2YS9sYW5nL1J1bm5hYmxlAQADcnVuACAADAAN" +
+    "AAAAAAAEAAAADgAPAAEAEAAAAB0AAQABAAAABSq3AAGxAAAAAQARAAAABgABAAAAEwACABIAEwAC" +
+    "ABAAAAAlAAIAAgAAAAkrEgK5AAMCALEAAAABABEAAAAKAAIAAAAVAAgAFgAUAAAAAgAVAAIAFgAT" +
+    "AAIAEAAAACUAAgACAAAACSsSBLkAAwIAsQAAAAEAEQAAAAoAAgAAABkACAAaABQAAAACABUAAQAX" +
+    "ABgAAgAQAAAAZQACAAMAAAAxLBIFuQADAgAqLLcABiwSB7kAAwIAK7kACAEALBIJuQADAgAqLLcA" +
+    "CiwSC7kAAwIAsQAAAAEAEQAAACIACAAAAB0ACAAeAA0AHwAVACAAGwAhACMAIgAoACMAMAAkABQA" +
+    "AAACABkAAQAaAAAAAgAb");
+  private static final byte[] DEX_BYTES = Base64.getDecoder().decode(
+    "ZGV4CjAzNQBc8wr9PcHqnOR61m+0kimXTSddVMToJPuYBQAAcAAAAHhWNBIAAAAAAAAAAOAEAAAc" +
+    "AAAAcAAAAAYAAADgAAAABAAAAPgAAAAAAAAAAAAAAAcAAAAoAQAAAQAAAGABAAAYBAAAgAEAAHoC" +
+    "AAB9AgAAgAIAAIgCAACOAgAAlgIAALcCAADWAgAA4wIAAAIDAAAWAwAALAMAAEADAABeAwAAfQMA" +
+    "AIQDAACUAwAAlwMAAJsDAACgAwAAqAMAALwDAADrAwAAGQQAAEcEAAB0BAAAeQQAAIAEAAAHAAAA" +
+    "CAAAAAkAAAAKAAAADQAAABAAAAAQAAAABQAAAAAAAAARAAAABQAAAGQCAAASAAAABQAAAGwCAAAR" +
+    "AAAABQAAAHQCAAAAAAAAAgAAAAAAAwAEAAAAAAADAA4AAAAAAAIAGgAAAAIAAAACAAAAAwAAABkA" +
+    "AAAEAAEAEwAAAAAAAAAAAAAAAgAAAAAAAAAPAAAAPAIAAMoEAAAAAAAAAQAAAKgEAAABAAAAuAQA" +
+    "AAEAAQABAAAAhwQAAAQAAABwEAQAAAAOAAMAAgACAAAAjAQAAAcAAAAbAAUAAAByIAYAAgAOAAAA" +
+    "AwACAAIAAACTBAAABwAAABsABgAAAHIgBgACAA4AAAAEAAMAAgAAAJoEAAAiAAAAGwAYAAAAciAG" +
+    "AAMAcCACADEAGwAWAAAAciAGAAMAchAFAAIAGwAXAAAAciAGAAMAcCABADEAGwAVAAAAciAGAAMA" +
+    "DgAAAAAAAAAAAAMAAAAAAAAAAQAAAIABAAACAAAAgAEAAAMAAACIAQAAAQAAAAIAAAACAAAAAwAE" +
+    "AAEAAAAEAAEoAAE8AAY8aW5pdD4ABD47KVYABkZpbmlzaAAfR29vZGJ5ZSAtIHByaXZhdGUgLSBU" +
+    "cmFuc2Zvcm1lZAAdSGVsbG8gLSBwcml2YXRlIC0gVHJhbnNmb3JtZWQAC0xUcmFuc2Zvcm07AB1M" +
+    "ZGFsdmlrL2Fubm90YXRpb24vU2lnbmF0dXJlOwASTGphdmEvbGFuZy9PYmplY3Q7ABRMamF2YS9s" +
+    "YW5nL1J1bm5hYmxlOwASTGphdmEvbGFuZy9TdHJpbmc7ABxMamF2YS91dGlsL2Z1bmN0aW9uL0Nv" +
+    "bnN1bWVyAB1MamF2YS91dGlsL2Z1bmN0aW9uL0NvbnN1bWVyOwAFU3RhcnQADlRyYW5zZm9ybS5q" +
+    "YXZhAAFWAAJWTAADVkxMAAZhY2NlcHQAEmVtaXR0ZXI6IGphY2stNC4xOQAtcG9zdCBGaW5pc2gg" +
+    "cHJpdmF0ZSBtZXRob2QgY2FsbCAtIFRyYW5zZm9ybWVkACxwb3N0IFN0YXJ0IHByaXZhdGUgbWV0" +
+    "aG9kIGNhbGwgLSBUcmFuc2Zvcm1lZAAscHJlIEZpbmlzaCBwcml2YXRlIG1ldGhvZCBjYWxsIC0g" +
+    "VHJhbnNmb3JtZWQAK3ByZSBTdGFydCBwcml2YXRlIG1ldGhvZCBjYWxsIC0gVHJhbnNmb3JtZWQA" +
+    "A3J1bgAFc2F5SGkABXZhbHVlABMABw4AGQEABw5pABUBAAcOaQAdAgAABw5pPGk8aTxpAAIBARsc" +
+    "BRcAFwwXARcLFwMCAQEbHAYXABcKFwwXARcLFwMAAAMBAICABJADAQKoAwECyAMDAegDDwAAAAAA" +
+    "AAABAAAAAAAAAAEAAAAcAAAAcAAAAAIAAAAGAAAA4AAAAAMAAAAEAAAA+AAAAAUAAAAHAAAAKAEA" +
+    "AAYAAAABAAAAYAEAAAMQAAACAAAAgAEAAAEgAAAEAAAAkAEAAAYgAAABAAAAPAIAAAEQAAADAAAA" +
+    "ZAIAAAIgAAAcAAAAegIAAAMgAAAEAAAAhwQAAAQgAAACAAAAqAQAAAAgAAABAAAAygQAAAAQAAAB" +
+    "AAAA4AQAAA==");
+
+  // A class that we can use to keep track of the output of this test.
+  private static class TestWatcher implements Consumer<String> {
+    private StringBuilder sb;
+    public TestWatcher() {
+      sb = new StringBuilder();
+    }
+
+    @Override
+    public void accept(String s) {
+      sb.append(s);
+      sb.append('\n');
+    }
+
+    public String getOutput() {
+      return sb.toString();
+    }
+
+    public void clear() {
+      sb = new StringBuilder();
+    }
+  }
+
+  public static void main(String[] args) {
+    System.loadLibrary(args[1]);
+    doTest(new Transform(), new TestWatcher());
+  }
+
+  // TODO Workaround to (1) inability to ensure that current_method is not put into a register by
+  // the JIT and/or (2) inability to deoptimize frames near runtime functions.
+  // TODO Fix one/both of these issues.
+  public static void doCall(Runnable r) {
+      r.run();
+  }
+
+  private static boolean interpreting = true;
+  private static boolean retry = false;
+
+  public static void doTest(Transform t, TestWatcher w) {
+    // Get the methods that need to be optimized.
+    Method say_hi_method;
+    Method do_call_method;
+    // Figure out if we can even JIT at all.
+    final boolean has_jit = hasJit();
+    try {
+      say_hi_method = Transform.class.getDeclaredMethod(
+          "sayHi", Runnable.class, Consumer.class);
+      do_call_method = Main.class.getDeclaredMethod("doCall", Runnable.class);
+    } catch (Exception e) {
+      System.out.println("Unable to find methods!");
+      e.printStackTrace();
+      return;
+    }
+    // Makes sure the stack is the way we want it for the test and does the redefinition. It will
+    // set the retry boolean to true if we need to go around again due to a bad stack.
+    Runnable do_redefinition = () -> {
+      if (has_jit &&
+          (Main.isInterpretedFunction(say_hi_method, true) ||
+           Main.isInterpretedFunction(do_call_method, false))) {
+        // Try again. We are not running the right jitted methods/cannot redefine them now.
+        retry = true;
+      } else {
+        // Actually do the redefinition. The stack looks good.
+        retry = false;
+        w.accept("transforming calling function");
+        doCommonClassRedefinition(Transform.class, CLASS_BYTES, DEX_BYTES);
+      }
+    };
+    // This does nothing.
+    Runnable noop = () -> {};
+    // This just prints something out to show we are running the Runnable.
+    Runnable say_nothing = () -> { w.accept("Not doing anything here"); };
+    // This checks to see if we have jitted the methods we are testing.
+    Runnable check_interpreting = () -> {
+      // TODO remove the second check when we remove the doCall function. We need to check that
+      // both of these functions aren't being interpreted because if sayHi is the test doesn't do
+      // anything and if doCall is then there will be a runtime call right above the sayHi
+      // function preventing sayHi from being deoptimized.
+      interpreting = has_jit && (Main.isInterpretedFunction(say_hi_method, true) ||
+                                 Main.isInterpretedFunction(do_call_method, false));
+    };
+    do {
+      w.clear();
+      // Wait for the methods to be jitted
+      long j = 0;
+      do {
+        for (int i = 0; i < 10000; i++) {
+          t.sayHi(noop, w);
+          j++;
+          // Clear so that we won't OOM if we go around a few times.
+          w.clear();
+        }
+        t.sayHi(check_interpreting, w);
+        if (j >= 1000000) {
+          System.out.println("FAIL: Could not make sayHi be Jitted!");
+          return;
+        }
+        j++;
+      } while(interpreting);
+      // Clear output. Now we try for real.
+      w.clear();
+      // Try and redefine.
+      t.sayHi(say_nothing, w);
+      t.sayHi(do_redefinition, w);
+      t.sayHi(say_nothing, w);
+    } while (retry);
+    // Print output of last run.
+    System.out.print(w.getOutput());
+  }
+
+  private static native boolean hasJit();
+
+  private static native boolean isInterpretedFunction(Method m, boolean require_deoptimizable);
+
+  // Transforms the class
+  private static native void doCommonClassRedefinition(Class<?> target,
+                                                       byte[] classfile,
+                                                       byte[] dexfile);
+}
diff --git a/test/916-obsolete-jit/src/Transform.java b/test/916-obsolete-jit/src/Transform.java
new file mode 100644
index 0000000..f4dcf09
--- /dev/null
+++ b/test/916-obsolete-jit/src/Transform.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.util.function.Consumer;
+
+class Transform {
+  private void Start(Consumer<String> reporter) {
+    reporter.accept("hello - private");
+  }
+
+  private void Finish(Consumer<String> reporter) {
+    reporter.accept("goodbye - private");
+  }
+
+  public void sayHi(Runnable r, Consumer<String> reporter) {
+    reporter.accept("Pre Start private method call");
+    Start(reporter);
+    reporter.accept("Post Start private method call");
+    // TODO Revisit with b/33616143
+    // TODO Uncomment this once either b/33630159 or b/33616143 are resolved.
+    // r.run();
+    // TODO This doCall function is a very temporary fix until we get either deoptimization near
+    // runtime frames working, forcing current method to be always read from the stack or both
+    // working.
+    Main.doCall(r);
+    reporter.accept("Pre Finish private method call");
+    Finish(reporter);
+    reporter.accept("Post Finish private method call");
+  }
+}
diff --git a/test/917-fields-transformation/run b/test/917-fields-transformation/run
index a434b63..4379349 100755
--- a/test/917-fields-transformation/run
+++ b/test/917-fields-transformation/run
@@ -14,30 +14,6 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-plugin=libopenjdkjvmtid.so
-agent=libtiagentd.so
-lib=tiagentd
-if  [[ "$@" == *"-O"* ]]; then
-  agent=libtiagent.so
-  plugin=libopenjdkjvmti.so
-  lib=tiagent
-fi
-
-if [[ "$@" == *"--jvm"* ]]; then
-  arg="jvm"
-else
-  arg="art"
-  if [[ "$@" != *"--debuggable"* ]]; then
-    other_args=" -Xcompiler-option --debuggable "
-  else
-    other_args=""
-  fi
-fi
-
 ./default-run "$@" --experimental agents \
                    --experimental runtime-plugins \
-                   --runtime-option -agentpath:${agent}=917-fields-transformation,${arg} \
-                   --android-runtime-option -Xplugin:${plugin} \
-                   --android-runtime-option -Xfully-deoptable \
-                   ${other_args} \
-                   --args ${lib}
+                   --jvmti
diff --git a/test/918-fields/build b/test/918-fields/build
new file mode 100755
index 0000000..898e2e5
--- /dev/null
+++ b/test/918-fields/build
@@ -0,0 +1,17 @@
+#!/bin/bash
+#
+# Copyright 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+./default-build "$@" --experimental agents
diff --git a/test/918-fields/expected.txt b/test/918-fields/expected.txt
new file mode 100644
index 0000000..39d3e70
--- /dev/null
+++ b/test/918-fields/expected.txt
@@ -0,0 +1,16 @@
+[PI, D, null]
+class java.lang.Math
+25
+false
+[value, I, null]
+class java.lang.Integer
+18
+false
+[this$0, LMain;, null]
+class Main$Foo
+4112
+true
+[VAL, I, null]
+interface Main$Bar
+25
+false
diff --git a/test/918-fields/fields.cc b/test/918-fields/fields.cc
new file mode 100644
index 0000000..4d2b34b
--- /dev/null
+++ b/test/918-fields/fields.cc
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "fields.h"
+
+#include <stdio.h>
+
+#include "base/macros.h"
+#include "jni.h"
+#include "openjdkjvmti/jvmti.h"
+#include "ScopedLocalRef.h"
+
+#include "ti-agent/common_helper.h"
+#include "ti-agent/common_load.h"
+
+namespace art {
+namespace Test918Fields {
+
+extern "C" JNIEXPORT jobjectArray JNICALL Java_Main_getFieldName(
+    JNIEnv* env, jclass klass, jobject field) {
+  jfieldID id = env->FromReflectedField(field);
+
+  char* name;
+  char* sig;
+  char* gen;
+  // Note: technically putting the caller class here is wrong, but we don't need it, anyways.
+  jvmtiError result = jvmti_env->GetFieldName(klass, id, &name, &sig, &gen);
+  if (result != JVMTI_ERROR_NONE) {
+    char* err;
+    jvmti_env->GetErrorName(result, &err);
+    printf("Failure running GetFieldName: %s\n", err);
+    jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(err));
+    return nullptr;
+  }
+
+  auto callback = [&](jint i) {
+    if (i == 0) {
+      return name == nullptr ? nullptr : env->NewStringUTF(name);
+    } else if (i == 1) {
+      return sig == nullptr ? nullptr : env->NewStringUTF(sig);
+    } else {
+      return gen == nullptr ? nullptr : env->NewStringUTF(gen);
+    }
+  };
+  jobjectArray ret = CreateObjectArray(env, 3, "java/lang/String", callback);
+
+  // Need to deallocate the strings.
+  if (name != nullptr) {
+    jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(name));
+  }
+  if (sig != nullptr) {
+    jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(sig));
+  }
+  if (gen != nullptr) {
+    jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(gen));
+  }
+
+  // Also run GetMethodName with all parameter pointers null to check for segfaults.
+  jvmtiError result2 = jvmti_env->GetFieldName(klass, id, nullptr, nullptr, nullptr);
+  if (result2 != JVMTI_ERROR_NONE) {
+    char* err;
+    jvmti_env->GetErrorName(result2, &err);
+    printf("Failure running GetFieldName(null, null, null): %s\n", err);
+    jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(err));
+    return nullptr;
+  }
+
+  return ret;
+}
+
+extern "C" JNIEXPORT jclass JNICALL Java_Main_getFieldDeclaringClass(
+    JNIEnv* env, jclass klass, jobject field) {
+  jfieldID id = env->FromReflectedField(field);
+
+  jclass declaring_class;
+  jvmtiError result = jvmti_env->GetFieldDeclaringClass(klass, id, &declaring_class);
+  if (result != JVMTI_ERROR_NONE) {
+    char* err;
+    jvmti_env->GetErrorName(result, &err);
+    printf("Failure running GetFieldDeclaringClass: %s\n", err);
+    jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(err));
+    return nullptr;
+  }
+
+  return declaring_class;
+}
+
+extern "C" JNIEXPORT jint JNICALL Java_Main_getFieldModifiers(
+    JNIEnv* env, jclass klass, jobject field) {
+  jfieldID id = env->FromReflectedField(field);
+
+  jint modifiers;
+  jvmtiError result = jvmti_env->GetFieldModifiers(klass, id, &modifiers);
+  if (result != JVMTI_ERROR_NONE) {
+    char* err;
+    jvmti_env->GetErrorName(result, &err);
+    printf("Failure running GetFieldModifiers: %s\n", err);
+    jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(err));
+    return 0;
+  }
+
+  return modifiers;
+}
+
+extern "C" JNIEXPORT jboolean JNICALL Java_Main_isFieldSynthetic(
+    JNIEnv* env, jclass klass, jobject field) {
+  jfieldID id = env->FromReflectedField(field);
+
+  jboolean synth;
+  jvmtiError result = jvmti_env->IsFieldSynthetic(klass, id, &synth);
+  if (result != JVMTI_ERROR_NONE) {
+    char* err;
+    jvmti_env->GetErrorName(result, &err);
+    printf("Failure running IsFieldSynthetic: %s\n", err);
+    jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(err));
+    return 0;
+  }
+
+  return synth;
+}
+
+// Don't do anything
+jint OnLoad(JavaVM* vm,
+            char* options ATTRIBUTE_UNUSED,
+            void* reserved ATTRIBUTE_UNUSED) {
+  if (vm->GetEnv(reinterpret_cast<void**>(&jvmti_env), JVMTI_VERSION_1_0)) {
+    printf("Unable to get jvmti env!\n");
+    return 1;
+  }
+  SetAllCapabilities(jvmti_env);
+  return 0;
+}
+
+}  // namespace Test918Fields
+}  // namespace art
diff --git a/test/918-fields/fields.h b/test/918-fields/fields.h
new file mode 100644
index 0000000..89bd161
--- /dev/null
+++ b/test/918-fields/fields.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ART_TEST_918_FIELDS_FIELDS_H_
+#define ART_TEST_918_FIELDS_FIELDS_H_
+
+#include <jni.h>
+
+namespace art {
+namespace Test918Fields {
+
+jint OnLoad(JavaVM* vm, char* options, void* reserved);
+
+}  // namespace Test918Fields
+}  // namespace art
+
+#endif  // ART_TEST_918_FIELDS_FIELDS_H_
diff --git a/test/918-fields/info.txt b/test/918-fields/info.txt
new file mode 100644
index 0000000..875a5f6
--- /dev/null
+++ b/test/918-fields/info.txt
@@ -0,0 +1 @@
+Tests basic functions in the jvmti plugin.
diff --git a/test/918-fields/run b/test/918-fields/run
new file mode 100755
index 0000000..4379349
--- /dev/null
+++ b/test/918-fields/run
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+./default-run "$@" --experimental agents \
+                   --experimental runtime-plugins \
+                   --jvmti
diff --git a/test/918-fields/src/Main.java b/test/918-fields/src/Main.java
new file mode 100644
index 0000000..8af6e7b
--- /dev/null
+++ b/test/918-fields/src/Main.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.lang.reflect.Field;
+import java.util.Arrays;
+
+public class Main {
+  public static void main(String[] args) throws Exception {
+    System.loadLibrary(args[1]);
+
+    doTest();
+  }
+
+  public static void doTest() throws Exception {
+    testField(Math.class, "PI");
+    testField(Integer.class, "value");
+    testField(Foo.class, "this$0");
+    testField(Bar.class, "VAL");
+  }
+
+  private static void testField(Class<?> base, String fieldName)
+      throws Exception {
+    Field f = base.getDeclaredField(fieldName);
+    String[] result = getFieldName(f);
+    System.out.println(Arrays.toString(result));
+
+    Class<?> declClass = getFieldDeclaringClass(f);
+    if (base != declClass) {
+      throw new RuntimeException("Declaring class not equal: " + base + " vs " + declClass);
+    }
+    System.out.println(declClass);
+
+    int modifiers = getFieldModifiers(f);
+    if (modifiers != f.getModifiers()) {
+      throw new RuntimeException("Modifiers not equal: " + f.getModifiers() + " vs " + modifiers);
+    }
+    System.out.println(modifiers);
+
+    boolean synth = isFieldSynthetic(f);
+    if (synth != f.isSynthetic()) {
+      throw new RuntimeException("Synthetic not equal: " + f.isSynthetic() + " vs " + synth);
+    }
+    System.out.println(synth);
+  }
+
+  private static native String[] getFieldName(Field f);
+  private static native Class<?> getFieldDeclaringClass(Field f);
+  private static native int getFieldModifiers(Field f);
+  private static native boolean isFieldSynthetic(Field f);
+
+  private class Foo {
+  }
+
+  private static interface Bar {
+    public static int VAL = 1;
+  }
+}
diff --git a/test/919-obsolete-fields/build b/test/919-obsolete-fields/build
new file mode 100755
index 0000000..898e2e5
--- /dev/null
+++ b/test/919-obsolete-fields/build
@@ -0,0 +1,17 @@
+#!/bin/bash
+#
+# Copyright 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+./default-build "$@" --experimental agents
diff --git a/test/919-obsolete-fields/expected.txt b/test/919-obsolete-fields/expected.txt
new file mode 100644
index 0000000..4caefc6
--- /dev/null
+++ b/test/919-obsolete-fields/expected.txt
@@ -0,0 +1,21 @@
+Pre Start private method call
+hello - private
+Post Start private method call
+Not doing anything here
+Pre Finish private method call
+goodbye - private
+Post Finish private method call
+Pre Start private method call
+hello - private
+Post Start private method call
+transforming calling function
+Pre Finish private method call
+Goodbye - private - Transformed
+Post Finish private method call
+pre Start private method call - Transformed
+Hello - private - Transformed
+post Start private method call - Transformed
+Not doing anything here
+pre Finish private method call - Transformed
+Goodbye - private - Transformed
+post Finish private method call - Transformed
diff --git a/test/919-obsolete-fields/info.txt b/test/919-obsolete-fields/info.txt
new file mode 100644
index 0000000..97739ca
--- /dev/null
+++ b/test/919-obsolete-fields/info.txt
@@ -0,0 +1 @@
+Tests obsolete method field access support
diff --git a/test/919-obsolete-fields/run b/test/919-obsolete-fields/run
new file mode 100755
index 0000000..4379349
--- /dev/null
+++ b/test/919-obsolete-fields/run
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+./default-run "$@" --experimental agents \
+                   --experimental runtime-plugins \
+                   --jvmti
diff --git a/test/919-obsolete-fields/src/Main.java b/test/919-obsolete-fields/src/Main.java
new file mode 100644
index 0000000..895c7a3
--- /dev/null
+++ b/test/919-obsolete-fields/src/Main.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.util.function.Consumer;
+import java.util.Base64;
+
+public class Main {
+
+  // What follows is the base64 encoded representation of the following class:
+  //
+  // import java.util.function.Consumer;
+  //
+  // class Transform {
+  //   private Consumer<String> reporter;
+  //   public Transform(Consumer<String> reporter) {
+  //     this.reporter = reporter;
+  //   }
+  //
+  //   private void Start() {
+  //     reporter.accept("Hello - private - Transformed");
+  //   }
+  //
+  //   private void Finish() {
+  //     reporter.accept("Goodbye - private - Transformed");
+  //   }
+  //
+  //   public void sayHi(Runnable r) {
+  //     reporter.accept("pre Start private method call - Transformed");
+  //     Start();
+  //     reporter.accept("post Start private method call - Transformed");
+  //     r.run();
+  //     reporter.accept("pre Finish private method call - Transformed");
+  //     Finish();
+  //     reporter.accept("post Finish private method call - Transformed");
+  //   }
+  // }
+  private static final byte[] CLASS_BYTES = Base64.getDecoder().decode(
+    "yv66vgAAADQANAoADgAfCQANACAIACELACIAIwgAJAgAJQoADQAmCAAnCwAoACkIACoKAA0AKwgA" +
+    "LAcALQcALgEACHJlcG9ydGVyAQAdTGphdmEvdXRpbC9mdW5jdGlvbi9Db25zdW1lcjsBAAlTaWdu" +
+    "YXR1cmUBADFMamF2YS91dGlsL2Z1bmN0aW9uL0NvbnN1bWVyPExqYXZhL2xhbmcvU3RyaW5nOz47" +
+    "AQAGPGluaXQ+AQAgKExqYXZhL3V0aWwvZnVuY3Rpb24vQ29uc3VtZXI7KVYBAARDb2RlAQAPTGlu" +
+    "ZU51bWJlclRhYmxlAQA0KExqYXZhL3V0aWwvZnVuY3Rpb24vQ29uc3VtZXI8TGphdmEvbGFuZy9T" +
+    "dHJpbmc7PjspVgEABVN0YXJ0AQADKClWAQAGRmluaXNoAQAFc2F5SGkBABcoTGphdmEvbGFuZy9S" +
+    "dW5uYWJsZTspVgEAClNvdXJjZUZpbGUBAA5UcmFuc2Zvcm0uamF2YQwAEwAZDAAPABABAB1IZWxs" +
+    "byAtIHByaXZhdGUgLSBUcmFuc2Zvcm1lZAcALwwAMAAxAQAfR29vZGJ5ZSAtIHByaXZhdGUgLSBU" +
+    "cmFuc2Zvcm1lZAEAK3ByZSBTdGFydCBwcml2YXRlIG1ldGhvZCBjYWxsIC0gVHJhbnNmb3JtZWQM" +
+    "ABgAGQEALHBvc3QgU3RhcnQgcHJpdmF0ZSBtZXRob2QgY2FsbCAtIFRyYW5zZm9ybWVkBwAyDAAz" +
+    "ABkBACxwcmUgRmluaXNoIHByaXZhdGUgbWV0aG9kIGNhbGwgLSBUcmFuc2Zvcm1lZAwAGgAZAQAt" +
+    "cG9zdCBGaW5pc2ggcHJpdmF0ZSBtZXRob2QgY2FsbCAtIFRyYW5zZm9ybWVkAQAJVHJhbnNmb3Jt" +
+    "AQAQamF2YS9sYW5nL09iamVjdAEAG2phdmEvdXRpbC9mdW5jdGlvbi9Db25zdW1lcgEABmFjY2Vw" +
+    "dAEAFShMamF2YS9sYW5nL09iamVjdDspVgEAEmphdmEvbGFuZy9SdW5uYWJsZQEAA3J1bgAgAA0A" +
+    "DgAAAAEAAgAPABAAAQARAAAAAgASAAQAAQATABQAAgAVAAAAKgACAAIAAAAKKrcAASortQACsQAA" +
+    "AAEAFgAAAA4AAwAAABUABAAWAAkAFwARAAAAAgAXAAIAGAAZAAEAFQAAACgAAgABAAAADCq0AAIS" +
+    "A7kABAIAsQAAAAEAFgAAAAoAAgAAABoACwAbAAIAGgAZAAEAFQAAACgAAgABAAAADCq0AAISBbkA" +
+    "BAIAsQAAAAEAFgAAAAoAAgAAAB4ACwAfAAEAGwAcAAEAFQAAAG8AAgACAAAAOyq0AAISBrkABAIA" +
+    "KrcAByq0AAISCLkABAIAK7kACQEAKrQAAhIKuQAEAgAqtwALKrQAAhIMuQAEAgCxAAAAAQAWAAAA" +
+    "IgAIAAAAIgALACMADwAkABoAJQAgACYAKwAnAC8AKAA6ACkAAQAdAAAAAgAe");
+  private static final byte[] DEX_BYTES = Base64.getDecoder().decode(
+    "ZGV4CjAzNQAw/b59wCwTlSVDmuhPEezuK3oe0rtT4ujMBQAAcAAAAHhWNBIAAAAAAAAAAAgFAAAd" +
+    "AAAAcAAAAAYAAADkAAAABAAAAPwAAAABAAAALAEAAAcAAAA0AQAAAQAAAGwBAABABAAAjAEAAJoC" +
+    "AACdAgAAoAIAAKgCAACsAgAAsgIAALoCAADbAgAA+gIAAAcDAAAmAwAAOgMAAFADAABkAwAAggMA" +
+    "AKEDAACoAwAAuAMAALsDAAC/AwAAxwMAANsDAAAKBAAAOAQAAGYEAACTBAAAnQQAAKIEAACpBAAA" +
+    "CAAAAAkAAAAKAAAACwAAAA4AAAARAAAAEQAAAAUAAAAAAAAAEgAAAAUAAACEAgAAEgAAAAUAAACM" +
+    "AgAAEgAAAAUAAACUAgAAAAAEABkAAAAAAAMAAgAAAAAAAAAFAAAAAAAAAA8AAAAAAAIAGwAAAAIA" +
+    "AAACAAAAAwAAABoAAAAEAAEAEwAAAAAAAAAAAAAAAgAAAAAAAAAQAAAAZAIAAO8EAAAAAAAAAQAA" +
+    "ANEEAAABAAAA3wQAAAIAAgABAAAAsAQAAAYAAABwEAQAAABbAQAADgADAAEAAgAAALgEAAAJAAAA" +
+    "VCAAABsBBgAAAHIgBgAQAA4AAAADAAEAAgAAAL4EAAAJAAAAVCAAABsBBwAAAHIgBgAQAA4AAAAE" +
+    "AAIAAgAAAMQEAAAqAAAAVCAAABsBGAAAAHIgBgAQAHAQAgACAFQgAAAbARYAAAByIAYAEAByEAUA" +
+    "AwBUIAAAGwEXAAAAciAGABAAcBABAAIAVCAAABsBFQAAAHIgBgAQAA4AAAAAAAEAAAABAAAAAAAA" +
+    "AAAAAACMAQAAAAAAAJQBAAABAAAAAgAAAAEAAAADAAAAAQAAAAQAASgAATwABjxpbml0PgACPjsA" +
+    "BD47KVYABkZpbmlzaAAfR29vZGJ5ZSAtIHByaXZhdGUgLSBUcmFuc2Zvcm1lZAAdSGVsbG8gLSBw" +
+    "cml2YXRlIC0gVHJhbnNmb3JtZWQAC0xUcmFuc2Zvcm07AB1MZGFsdmlrL2Fubm90YXRpb24vU2ln" +
+    "bmF0dXJlOwASTGphdmEvbGFuZy9PYmplY3Q7ABRMamF2YS9sYW5nL1J1bm5hYmxlOwASTGphdmEv" +
+    "bGFuZy9TdHJpbmc7ABxMamF2YS91dGlsL2Z1bmN0aW9uL0NvbnN1bWVyAB1MamF2YS91dGlsL2Z1" +
+    "bmN0aW9uL0NvbnN1bWVyOwAFU3RhcnQADlRyYW5zZm9ybS5qYXZhAAFWAAJWTAAGYWNjZXB0ABJl" +
+    "bWl0dGVyOiBqYWNrLTQuMTkALXBvc3QgRmluaXNoIHByaXZhdGUgbWV0aG9kIGNhbGwgLSBUcmFu" +
+    "c2Zvcm1lZAAscG9zdCBTdGFydCBwcml2YXRlIG1ldGhvZCBjYWxsIC0gVHJhbnNmb3JtZWQALHBy" +
+    "ZSBGaW5pc2ggcHJpdmF0ZSBtZXRob2QgY2FsbCAtIFRyYW5zZm9ybWVkACtwcmUgU3RhcnQgcHJp" +
+    "dmF0ZSBtZXRob2QgY2FsbCAtIFRyYW5zZm9ybWVkAAhyZXBvcnRlcgADcnVuAAVzYXlIaQAFdmFs" +
+    "dWUAFQEABw48LQAeAAcOhwAaAAcOhwAiAQAHDoc8hzyHPIcAAgEBHBwEFw0XARcMFwMCAQEcHAUX" +
+    "ABcNFwEXDBcEAAEDAQACAIGABJwDAQK4AwEC3AMDAYAEABAAAAAAAAAAAQAAAAAAAAABAAAAHQAA" +
+    "AHAAAAACAAAABgAAAOQAAAADAAAABAAAAPwAAAAEAAAAAQAAACwBAAAFAAAABwAAADQBAAAGAAAA" +
+    "AQAAAGwBAAADEAAAAgAAAIwBAAABIAAABAAAAJwBAAAGIAAAAQAAAGQCAAABEAAAAwAAAIQCAAAC" +
+    "IAAAHQAAAJoCAAADIAAABAAAALAEAAAEIAAAAgAAANEEAAAAIAAAAQAAAO8EAAAAEAAAAQAAAAgF" +
+    "AAA=");
+
+  // A class that we can use to keep track of the output of this test.
+  private static class TestWatcher implements Consumer<String> {
+    private StringBuilder sb;
+    public TestWatcher() {
+      sb = new StringBuilder();
+    }
+
+    @Override
+    public void accept(String s) {
+      sb.append(s);
+      sb.append('\n');
+    }
+
+    public String getOutput() {
+      return sb.toString();
+    }
+  }
+
+  public static void main(String[] args) {
+    System.loadLibrary(args[1]);
+    TestWatcher w = new TestWatcher();
+    doTest(new Transform(w), w);
+  }
+
+  // TODO Workaround to (1) inability to ensure that current_method is not put into a register by
+  // the JIT and/or (2) inability to deoptimize frames near runtime functions.
+  // TODO Fix one/both of these issues.
+  public static void doCall(Runnable r) {
+      r.run();
+  }
+
+  private static boolean interpreting = true;
+  private static boolean retry = false;
+
+  public static void doTest(Transform t, TestWatcher w) {
+    Runnable do_redefinition = () -> {
+      w.accept("transforming calling function");
+      doCommonClassRedefinition(Transform.class, CLASS_BYTES, DEX_BYTES);
+    };
+    // This just prints something out to show we are running the Runnable.
+    Runnable say_nothing = () -> { w.accept("Not doing anything here"); };
+
+    // Try and redefine.
+    t.sayHi(say_nothing);
+    t.sayHi(do_redefinition);
+    t.sayHi(say_nothing);
+
+    // Print output of last run.
+    System.out.print(w.getOutput());
+  }
+
+  // Transforms the class
+  private static native void doCommonClassRedefinition(Class<?> target,
+                                                       byte[] classfile,
+                                                       byte[] dexfile);
+}
diff --git a/test/919-obsolete-fields/src/Transform.java b/test/919-obsolete-fields/src/Transform.java
new file mode 100644
index 0000000..abd1d19
--- /dev/null
+++ b/test/919-obsolete-fields/src/Transform.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.util.function.Consumer;
+
+class Transform {
+  private Consumer<String> reporter;
+  public Transform(Consumer<String> reporter) {
+    this.reporter = reporter;
+  }
+
+  private void Start() {
+    reporter.accept("hello - private");
+  }
+
+  private void Finish() {
+    reporter.accept("goodbye - private");
+  }
+
+  public void sayHi(Runnable r) {
+    reporter.accept("Pre Start private method call");
+    Start();
+    reporter.accept("Post Start private method call");
+    // TODO Revist with b/33616143
+    // TODO Uncomment this
+    // r.run();
+    // TODO This is a very temporary fix until we get either deoptimization near runtime frames
+    // working, forcing current method to be always read from the stack or both working.
+    Main.doCall(r);
+    reporter.accept("Pre Finish private method call");
+    Finish();
+    reporter.accept("Post Finish private method call");
+  }
+}
diff --git a/test/920-objects/build b/test/920-objects/build
new file mode 100755
index 0000000..898e2e5
--- /dev/null
+++ b/test/920-objects/build
@@ -0,0 +1,17 @@
+#!/bin/bash
+#
+# Copyright 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+./default-build "$@" --experimental agents
diff --git a/test/920-objects/expected.txt b/test/920-objects/expected.txt
new file mode 100644
index 0000000..80feeb9
--- /dev/null
+++ b/test/920-objects/expected.txt
@@ -0,0 +1,10 @@
+class java.lang.Object 8
+class java.lang.Object 8
+class [I 12
+class [I 16
+class [I 20
+class [D 16
+class [D 24
+class [D 32
+class java.lang.String 24
+class java.lang.String 24
diff --git a/test/920-objects/info.txt b/test/920-objects/info.txt
new file mode 100644
index 0000000..875a5f6
--- /dev/null
+++ b/test/920-objects/info.txt
@@ -0,0 +1 @@
+Tests basic functions in the jvmti plugin.
diff --git a/test/920-objects/objects.cc b/test/920-objects/objects.cc
new file mode 100644
index 0000000..886dd0e
--- /dev/null
+++ b/test/920-objects/objects.cc
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "objects.h"
+
+#include <stdio.h>
+
+#include "base/macros.h"
+#include "jni.h"
+#include "openjdkjvmti/jvmti.h"
+#include "ScopedLocalRef.h"
+
+#include "ti-agent/common_helper.h"
+#include "ti-agent/common_load.h"
+
+namespace art {
+namespace Test920Objects {
+
+extern "C" JNIEXPORT jlong JNICALL Java_Main_getObjectSize(
+    JNIEnv* env ATTRIBUTE_UNUSED, jclass klass ATTRIBUTE_UNUSED, jobject object) {
+  jlong size;
+
+  jvmtiError result = jvmti_env->GetObjectSize(object, &size);
+  if (result != JVMTI_ERROR_NONE) {
+    char* err;
+    jvmti_env->GetErrorName(result, &err);
+    printf("Failure running GetObjectSize: %s\n", err);
+    jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(err));
+    return -1;
+  }
+
+  return size;
+}
+
+extern "C" JNIEXPORT jint JNICALL Java_Main_getObjectHashCode(
+    JNIEnv* env ATTRIBUTE_UNUSED, jclass klass ATTRIBUTE_UNUSED, jobject object) {
+  jint hash;
+
+  jvmtiError result = jvmti_env->GetObjectHashCode(object, &hash);
+  if (result != JVMTI_ERROR_NONE) {
+    char* err;
+    jvmti_env->GetErrorName(result, &err);
+    printf("Failure running GetObjectHashCode: %s\n", err);
+    jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(err));
+    return -1;
+  }
+
+  return hash;
+}
+
+// Don't do anything
+jint OnLoad(JavaVM* vm,
+            char* options ATTRIBUTE_UNUSED,
+            void* reserved ATTRIBUTE_UNUSED) {
+  if (vm->GetEnv(reinterpret_cast<void**>(&jvmti_env), JVMTI_VERSION_1_0)) {
+    printf("Unable to get jvmti env!\n");
+    return 1;
+  }
+  SetAllCapabilities(jvmti_env);
+  return 0;
+}
+
+}  // namespace Test920Objects
+}  // namespace art
diff --git a/test/920-objects/objects.h b/test/920-objects/objects.h
new file mode 100644
index 0000000..5f21e7b
--- /dev/null
+++ b/test/920-objects/objects.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ART_TEST_920_OBJECTS_OBJECTS_H_
+#define ART_TEST_920_OBJECTS_OBJECTS_H_
+
+#include <jni.h>
+
+namespace art {
+namespace Test920Objects {
+
+jint OnLoad(JavaVM* vm, char* options, void* reserved);
+
+}  // namespace Test920Objects
+}  // namespace art
+
+#endif  // ART_TEST_920_OBJECTS_OBJECTS_H_
diff --git a/test/920-objects/run b/test/920-objects/run
new file mode 100755
index 0000000..4379349
--- /dev/null
+++ b/test/920-objects/run
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+./default-run "$@" --experimental agents \
+                   --experimental runtime-plugins \
+                   --jvmti
diff --git a/test/920-objects/src/Main.java b/test/920-objects/src/Main.java
new file mode 100644
index 0000000..5dbe1a7
--- /dev/null
+++ b/test/920-objects/src/Main.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.lang.reflect.Field;
+import java.util.Arrays;
+
+public class Main {
+  public static void main(String[] args) throws Exception {
+    System.loadLibrary(args[1]);
+
+    doTest();
+  }
+
+  public static void doTest() throws Exception {
+    testObjectSize(new Object());
+    testObjectSize(new Object());
+
+    testObjectSize(new int[0]);
+    testObjectSize(new int[1]);
+    testObjectSize(new int[2]);
+
+    testObjectSize(new double[0]);
+    testObjectSize(new double[1]);
+    testObjectSize(new double[2]);
+
+    testObjectSize(new String("abc"));
+    testObjectSize(new String("wxyz"));
+
+    testObjectHash();
+  }
+
+  private static void testObjectSize(Object o) {
+    System.out.println(o.getClass() + " " + getObjectSize(o));
+  }
+
+  private static void testObjectHash() {
+    Object[] objects = new Object[] {
+        new Object(),
+        new Object(),
+
+        new MyHash(1),
+        new MyHash(1),
+        new MyHash(2)
+    };
+
+    int hashes[] = new int[objects.length];
+
+    for (int i = 0; i < objects.length; i++) {
+      hashes[i] = getObjectHashCode(objects[i]);
+    }
+
+    // Implementation detail: we use the identity hashcode, for simplicity.
+    for (int i = 0; i < objects.length; i++) {
+      int ihash = System.identityHashCode(objects[i]);
+      if (hashes[i] != ihash) {
+        throw new RuntimeException(objects[i] + ": " + hashes[i] + " vs " + ihash);
+      }
+    }
+
+    Runtime.getRuntime().gc();
+    Runtime.getRuntime().gc();
+
+    for (int i = 0; i < objects.length; i++) {
+      int newhash = getObjectHashCode(objects[i]);
+      if (hashes[i] != newhash) {
+        throw new RuntimeException(objects[i] + ": " + hashes[i] + " vs " + newhash);
+      }
+    }
+  }
+
+  private static native long getObjectSize(Object o);
+  private static native int getObjectHashCode(Object o);
+
+  private static class MyHash {
+    private int hash;
+
+    public MyHash(int h) {
+      hash = h;
+    }
+
+    public int hashCode() {
+      return hash;
+    }
+  }
+}
diff --git a/test/Android.bp b/test/Android.bp
index 2625f56..5a8f43e 100644
--- a/test/Android.bp
+++ b/test/Android.bp
@@ -243,6 +243,9 @@
     name: "libtiagent-defaults",
     defaults: ["libartagent-defaults"],
     srcs: [
+        // This is to get the IsInterpreted native method.
+        "common/stack_inspect.cc",
+        "common/runtime_state.cc",
         "ti-agent/common_load.cc",
         "ti-agent/common_helper.cc",
         "901-hello-ti-agent/basics.cc",
@@ -257,6 +260,8 @@
         "911-get-stack-trace/stack_trace.cc",
         "912-classes/classes.cc",
         "913-heaps/heaps.cc",
+        "918-fields/fields.cc",
+        "920-objects/objects.cc",
     ],
     shared_libs: [
         "libbase",
diff --git a/test/Android.run-test.mk b/test/Android.run-test.mk
index fdd5b60..dd755b1 100644
--- a/test/Android.run-test.mk
+++ b/test/Android.run-test.mk
@@ -286,7 +286,13 @@
   911-get-stack-trace \
   912-classes \
   913-heaps \
+  914-hello-obsolescence \
+  915-obsolete-2 \
+  916-obsolete-jit \
   917-fields-transformation \
+  918-fields \
+  919-obsolete-fields \
+  920-objects \
 
 ifneq (,$(filter target,$(TARGET_TYPES)))
   ART_TEST_KNOWN_BROKEN += $(call all-run-test-names,target,$(RUN_TYPES),$(PREBUILD_TYPES), \
@@ -539,16 +545,14 @@
 # Test 906 iterates the heap filtering with different options. No instances should be created
 # between those runs to be able to have precise checks.
 # Test 902 hits races with the JIT compiler. b/32821077
-# Test 626-const-class-linking can deadlock with JIT. b/33567581
 # Test 629 requires compilation.
-# Test 914, 915, 917, & 918 are very sensitive to the exact state of the stack,
+# Test 914, 915, 917, & 919 are very sensitive to the exact state of the stack,
 # including the jit-inserted runtime frames. This causes them to be somewhat
 # flaky as JIT tests. This should be fixed once b/33630159 or b/33616143 are
 # resolved but until then just disable them. Test 916 already checks this
 # feature for JIT use cases in a way that is resilient to the jit frames.
 TEST_ART_BROKEN_JIT_RUN_TESTS := \
   137-cfi \
-  626-const-class-linking \
   629-vdex-speed \
   902-hello-transformation \
   904-object-allocation \
@@ -556,7 +560,7 @@
   914-hello-obsolescence \
   915-obsolete-2 \
   917-fields-transformation \
-  918-obsolete-fields \
+  919-obsolete-fields \
 
 ifneq (,$(filter jit,$(COMPILER_TYPES)))
   ART_TEST_KNOWN_BROKEN += $(call all-run-test-names,$(TARGET_TYPES),$(RUN_TYPES),$(PREBUILD_TYPES), \
@@ -1020,10 +1024,11 @@
     test_groups += ART_RUN_TEST_$$(uc_host_or_target)_NO_IMAGE_RULES
     run_test_options += --no-image
     # Add the core dependency. This is required for pre-building.
+    # Use the PIC image, as it is the default in run-test, to match dependencies.
     ifeq ($(1),host)
-      prereq_rule += $$(HOST_CORE_IMAGE_$$(image_suffix)_no-pic_$(13))
+      prereq_rule += $$(HOST_CORE_IMAGE_$$(image_suffix)_pic_$(13))
     else
-      prereq_rule += $$(TARGET_CORE_IMAGE_$$(image_suffix)_no-pic_$(13))
+      prereq_rule += $$(TARGET_CORE_IMAGE_$$(image_suffix)_pic_$(13))
     endif
   else
     ifeq ($(9),npicimage)
diff --git a/test/common/runtime_state.cc b/test/common/runtime_state.cc
index f26e122..7451cf9 100644
--- a/test/common/runtime_state.cc
+++ b/test/common/runtime_state.cc
@@ -19,6 +19,7 @@
 #include "base/enums.h"
 #include "base/logging.h"
 #include "dex_file-inl.h"
+#include "instrumentation.h"
 #include "jit/jit.h"
 #include "jit/jit_code_cache.h"
 #include "mirror/class-inl.h"
@@ -30,6 +31,16 @@
 
 namespace art {
 
+// public static native boolean hasJit();
+
+extern "C" JNIEXPORT jboolean JNICALL Java_Main_hasJit(JNIEnv*, jclass) {
+  Runtime* runtime = Runtime::Current();
+  return runtime != nullptr
+      && runtime->GetJit() != nullptr
+      && runtime->GetInstrumentation()->GetCurrentInstrumentationLevel() !=
+            instrumentation::Instrumentation::InstrumentationLevel::kInstrumentWithInterpreter;
+}
+
 // public static native boolean hasOatFile();
 
 extern "C" JNIEXPORT jboolean JNICALL Java_Main_hasOatFile(JNIEnv* env, jclass cls) {
diff --git a/test/common/stack_inspect.cc b/test/common/stack_inspect.cc
index 4df2d47..df7fa20 100644
--- a/test/common/stack_inspect.cc
+++ b/test/common/stack_inspect.cc
@@ -18,6 +18,7 @@
 
 #include "base/logging.h"
 #include "dex_file-inl.h"
+#include "jni_internal.h"
 #include "mirror/class-inl.h"
 #include "nth_caller_visitor.h"
 #include "oat_file.h"
@@ -52,6 +53,89 @@
   return IsInterpreted(env, klass, 1);
 }
 
+// public static native boolean isInterpreted(int depth);
+
+extern "C" JNIEXPORT jboolean JNICALL Java_Main_isInterpretedAt(JNIEnv* env,
+                                                                jclass klass,
+                                                                jint depth) {
+  return IsInterpreted(env, klass, depth);
+}
+
+
+// public static native boolean isInterpretedFunction(String smali);
+
+// TODO Remove 'allow_runtime_frames' option once we have deoptimization through runtime frames.
+struct MethodIsInterpretedVisitor : public StackVisitor {
+ public:
+  MethodIsInterpretedVisitor(Thread* thread, ArtMethod* goal, bool require_deoptable)
+      : StackVisitor(thread, nullptr, StackVisitor::StackWalkKind::kIncludeInlinedFrames),
+        goal_(goal),
+        method_is_interpreted_(true),
+        method_found_(false),
+        prev_was_runtime_(true),
+        require_deoptable_(require_deoptable) {}
+
+  virtual bool VisitFrame() OVERRIDE REQUIRES_SHARED(Locks::mutator_lock_) {
+    if (goal_ == GetMethod()) {
+      method_is_interpreted_ = (require_deoptable_ && prev_was_runtime_) || IsShadowFrame();
+      method_found_ = true;
+      return false;
+    }
+    prev_was_runtime_ = GetMethod()->IsRuntimeMethod();
+    return true;
+  }
+
+  bool IsInterpreted() {
+    return method_is_interpreted_;
+  }
+
+  bool IsFound() {
+    return method_found_;
+  }
+
+ private:
+  const ArtMethod* goal_;
+  bool method_is_interpreted_;
+  bool method_found_;
+  bool prev_was_runtime_;
+  bool require_deoptable_;
+};
+
+// TODO Remove 'require_deoptimizable' option once we have deoptimization through runtime frames.
+extern "C" JNIEXPORT jboolean JNICALL Java_Main_isInterpretedFunction(
+    JNIEnv* env, jclass klass ATTRIBUTE_UNUSED, jobject method, jboolean require_deoptimizable) {
+  // Return false if this seems to not be an ART runtime.
+  if (Runtime::Current() == nullptr) {
+    return JNI_FALSE;
+  }
+  if (method == nullptr) {
+    env->ThrowNew(env->FindClass("java/lang/NullPointerException"), "method is null!");
+    return JNI_FALSE;
+  }
+  jmethodID id = env->FromReflectedMethod(method);
+  if (id == nullptr) {
+    env->ThrowNew(env->FindClass("java/lang/Error"), "Unable to interpret method argument!");
+    return JNI_FALSE;
+  }
+  bool result;
+  bool found;
+  {
+    ScopedObjectAccess soa(env);
+    ArtMethod* goal = jni::DecodeArtMethod(id);
+    MethodIsInterpretedVisitor v(soa.Self(), goal, require_deoptimizable);
+    v.WalkStack();
+    bool enters_interpreter = Runtime::Current()->GetClassLinker()->IsQuickToInterpreterBridge(
+        goal->GetEntryPointFromQuickCompiledCode());
+    result = (v.IsInterpreted() || enters_interpreter);
+    found = v.IsFound();
+  }
+  if (!found) {
+    env->ThrowNew(env->FindClass("java/lang/Error"), "Unable to find given method in stack!");
+    return JNI_FALSE;
+  }
+  return result;
+}
+
 // public static native void assertIsInterpreted();
 
 extern "C" JNIEXPORT void JNICALL Java_Main_assertIsInterpreted(JNIEnv* env, jclass klass) {
diff --git a/test/etc/default-build b/test/etc/default-build
index 51ae175..e9e3886 100755
--- a/test/etc/default-build
+++ b/test/etc/default-build
@@ -69,6 +69,7 @@
 
 # Setup experimental flag mappings in a bash associative array.
 declare -A JACK_EXPERIMENTAL_ARGS
+JACK_EXPERIMENTAL_ARGS["agents"]="-D jack.java.source.version=1.8 -D jack.android.min-api-level=24"
 JACK_EXPERIMENTAL_ARGS["default-methods"]="-D jack.java.source.version=1.8 -D jack.android.min-api-level=24"
 JACK_EXPERIMENTAL_ARGS["lambdas"]="-D jack.java.source.version=1.8 -D jack.android.min-api-level=24"
 JACK_EXPERIMENTAL_ARGS["method-handles"]="-D jack.java.source.version=1.7 -D jack.android.min-api-level=o-b1"
@@ -76,12 +77,14 @@
 declare -A SMALI_EXPERIMENTAL_ARGS
 SMALI_EXPERIMENTAL_ARGS["default-methods"]="--api-level 24"
 SMALI_EXPERIMENTAL_ARGS["method-handles"]="--api-level 26"
+SMALI_EXPERIMENTAL_ARGS["agents"]="--api-level 26"
 
 declare -A JAVAC_EXPERIMENTAL_ARGS
 JAVAC_EXPERIMENTAL_ARGS["default-methods"]="-source 1.8 -target 1.8"
 JAVAC_EXPERIMENTAL_ARGS["lambdas"]="-source 1.8 -target 1.8"
 JAVAC_EXPERIMENTAL_ARGS["method-handles"]="-source 1.8 -target 1.8"
 JAVAC_EXPERIMENTAL_ARGS[${DEFAULT_EXPERIMENT}]="-source 1.7 -target 1.7"
+JAVAC_EXPERIMENTAL_ARGS["agents"]="-source 1.8 -target 1.8"
 
 while true; do
   if [ "x$1" = "x--dx-option" ]; then
@@ -126,6 +129,16 @@
 # Be sure to get any default arguments if not doing any experiments.
 EXPERIMENTAL="${EXPERIMENTAL} ${DEFAULT_EXPERIMENT}"
 
+if [ "${JACK_SERVER}" = "false" ]; then
+  # Run in single-threaded mode for the continuous buildbot.
+  JACK_ARGS="${JACK_ARGS} -D sched.runner=single-threaded"
+else
+  # Run with 4 threads to reduce memory footprint and thread contention.
+  JACK_ARGS="${JACK_ARGS} -D sched.runner=multi-threaded"
+  JACK_ARGS="${JACK_ARGS} -D sched.runner.thread.kind=fixed"
+  JACK_ARGS="${JACK_ARGS} -D sched.runner.thread.fixed.count=4"
+fi
+
 # Add args from the experimental mappings.
 for experiment in ${EXPERIMENTAL}; do
   JACK_ARGS="${JACK_ARGS} ${JACK_EXPERIMENTAL_ARGS[${experiment}]}"
diff --git a/test/etc/run-test-jar b/test/etc/run-test-jar
index 566f7ba..8245947 100755
--- a/test/etc/run-test-jar
+++ b/test/etc/run-test-jar
@@ -30,6 +30,7 @@
 INTERPRETER="n"
 JIT="n"
 INVOKE_WITH=""
+IS_JVMTI_TEST="n"
 ISA=x86
 LIBRARY_DIRECTORY="lib"
 TEST_DIRECTORY="nativetest"
@@ -59,14 +60,18 @@
 EXTERNAL_LOG_TAGS="n" # if y respect externally set ANDROID_LOG_TAGS.
 DRY_RUN="n" # if y prepare to run the test but don't run it.
 TEST_VDEX="n"
+TEST_IS_NDEBUG="n"
 APP_IMAGE="y"
 
 while true; do
     if [ "x$1" = "x--quiet" ]; then
         QUIET="y"
         shift
+    elif [ "x$1" = "x--jvmti" ]; then
+        IS_JVMTI_TEST="y"
+        shift
     elif [ "x$1" = "x-O" ]; then
-        # Ignore this option.
+        TEST_IS_NDEBUG="y"
         shift
     elif [ "x$1" = "x--lib" ]; then
         shift
@@ -382,6 +387,28 @@
     fi
 fi
 
+if [ "$IS_JVMTI_TEST" = "y" ]; then
+  plugin=libopenjdkjvmtid.so
+  agent=libtiagentd.so
+  lib=tiagentd
+  if  [[ "$TEST_IS_NDEBUG" = "y" ]]; then
+    agent=libtiagent.so
+    plugin=libopenjdkjvmti.so
+    lib=tiagent
+  fi
+
+  ARGS="${ARGS} ${lib}"
+  if [[ "$USE_JVM" = "y" ]]; then
+    FLAGS="${FLAGS} -agentpath:${agent}=${TEST_NAME},jvm"
+  else
+    FLAGS="${FLAGS} -agentpath:${agent}=${TEST_NAME},art"
+    FLAGS="${FLAGS} -Xplugin:${plugin}"
+    FLAGS="${FLAGS} -Xfully-deoptable"
+    # Always make the compilation be debuggable.
+    COMPILE_FLAGS="${COMPILE_FLAGS} --debuggable"
+  fi
+fi
+
 JNI_OPTS="-Xjnigreflimit:512 -Xcheck:jni"
 
 if [ "$RELOCATE" = "y" ]; then
diff --git a/test/ti-agent/common_helper.cc b/test/ti-agent/common_helper.cc
index 3e2b168..ebf1e46 100644
--- a/test/ti-agent/common_helper.cc
+++ b/test/ti-agent/common_helper.cc
@@ -18,8 +18,11 @@
 
 #include <stdio.h>
 
+#include "art_method.h"
 #include "jni.h"
 #include "openjdkjvmti/jvmti.h"
+#include "scoped_thread_state_change-inl.h"
+#include "stack.h"
 #include "ti-agent/common_load.h"
 #include "utils.h"
 
diff --git a/test/ti-agent/common_load.cc b/test/ti-agent/common_load.cc
index 3886148..2f4af9f 100644
--- a/test/ti-agent/common_load.cc
+++ b/test/ti-agent/common_load.cc
@@ -37,6 +37,8 @@
 #include "911-get-stack-trace/stack_trace.h"
 #include "912-classes/classes.h"
 #include "913-heaps/heaps.h"
+#include "918-fields/fields.h"
+#include "920-objects/objects.h"
 
 namespace art {
 
@@ -66,7 +68,13 @@
   { "911-get-stack-trace", Test911GetStackTrace::OnLoad, nullptr },
   { "912-classes", Test912Classes::OnLoad, nullptr },
   { "913-heaps", Test913Heaps::OnLoad, nullptr },
+  { "914-hello-obsolescence", common_redefine::OnLoad, nullptr },
+  { "915-obsolete-2", common_redefine::OnLoad, nullptr },
+  { "916-obsolete-jit", common_redefine::OnLoad, nullptr },
   { "917-fields-transformation", common_redefine::OnLoad, nullptr },
+  { "918-fields", Test918Fields::OnLoad, nullptr },
+  { "919-obsolete-fields", common_redefine::OnLoad, nullptr },
+  { "920-objects", Test920Objects::OnLoad, nullptr },
 };
 
 static AgentLib* FindAgent(char* name) {