Merge "Revert^2 "Remove support for Valgrind in ART.""
diff --git a/compiler/debug/elf_debug_info_writer.h b/compiler/debug/elf_debug_info_writer.h
index f2002a0..f2a942f 100644
--- a/compiler/debug/elf_debug_info_writer.h
+++ b/compiler/debug/elf_debug_info_writer.h
@@ -210,8 +210,7 @@
         code_info.reset(new CodeInfo(mi->code_info));
         for (size_t s = 0; s < code_info->GetNumberOfStackMaps(); ++s) {
           const StackMap stack_map = code_info->GetStackMapAt(s);
-          dex_reg_maps.push_back(code_info->GetDexRegisterMapOf(
-              stack_map, accessor.RegistersSize()));
+          dex_reg_maps.push_back(code_info->GetDexRegisterMapOf(stack_map));
         }
       }
 
diff --git a/compiler/debug/elf_debug_loc_writer.h b/compiler/debug/elf_debug_loc_writer.h
index 8cb4e55..4009acb 100644
--- a/compiler/debug/elf_debug_loc_writer.h
+++ b/compiler/debug/elf_debug_loc_writer.h
@@ -147,7 +147,7 @@
     DexRegisterLocation reg_hi = DexRegisterLocation::None();
     DCHECK_LT(stack_map_index, dex_register_maps.size());
     DexRegisterMap dex_register_map = dex_register_maps[stack_map_index];
-    DCHECK(dex_register_map.IsValid());
+    DCHECK(!dex_register_map.empty());
     CodeItemDataAccessor accessor(*method_info->dex_file, method_info->code_item);
     reg_lo = dex_register_map.GetDexRegisterLocation(vreg);
     if (is64bitValue) {
diff --git a/compiler/dex/dex_to_dex_decompiler_test.cc b/compiler/dex/dex_to_dex_decompiler_test.cc
index 1fe42ad..d4a9ba5 100644
--- a/compiler/dex/dex_to_dex_decompiler_test.cc
+++ b/compiler/dex/dex_to_dex_decompiler_test.cc
@@ -82,9 +82,8 @@
     ASSERT_NE(0, cmp);
 
     // Unquicken the dex file.
-    for (uint32_t i = 0; i < updated_dex_file->NumClassDefs(); ++i) {
+    for (ClassAccessor accessor : updated_dex_file->GetClasses()) {
       // Unquicken each method.
-      ClassAccessor accessor(*updated_dex_file, updated_dex_file->GetClassDef(i));
       for (const ClassAccessor::Method& method : accessor.GetMethods()) {
         CompiledMethod* compiled_method = compiler_driver_->GetCompiledMethod(
             method.GetReference());
diff --git a/compiler/driver/compiler_driver.cc b/compiler/driver/compiler_driver.cc
index 653e9ed..6cb3936 100644
--- a/compiler/driver/compiler_driver.cc
+++ b/compiler/driver/compiler_driver.cc
@@ -1685,16 +1685,14 @@
 
 bool CompilerDriver::RequiresConstructorBarrier(const DexFile& dex_file,
                                                 uint16_t class_def_idx) const {
-  ClassAccessor accessor(dex_file, dex_file.GetClassDef(class_def_idx));
-  bool has_is_final = false;
+  ClassAccessor accessor(dex_file, class_def_idx);
   // We require a constructor barrier if there are final instance fields.
-  accessor.VisitFields(/*static*/ VoidFunctor(),
-                       [&](const ClassAccessor::Field& field) {
+  for (const ClassAccessor::Field& field : accessor.GetInstanceFields()) {
     if (field.IsFinal()) {
-      has_is_final = true;
+      return true;
     }
-  });
-  return has_is_final;
+  }
+  return false;
 }
 
 class ResolveClassFieldsAndMethodsVisitor : public CompilationVisitor {
@@ -1744,7 +1742,7 @@
     // fields are assigned within the lock held for class initialization.
     bool requires_constructor_barrier = false;
 
-    ClassAccessor accessor(dex_file, class_def);
+    ClassAccessor accessor(dex_file, class_def_index);
     // Optionally resolve fields and methods and figure out if we need a constructor barrier.
     auto method_visitor = [&](const ClassAccessor::Method& method)
         REQUIRES_SHARED(Locks::mutator_lock_) {
@@ -1926,13 +1924,12 @@
     // Fetch the list of unverified classes.
     const std::set<dex::TypeIndex>& unverified_classes =
         verifier_deps->GetUnverifiedClasses(*dex_file);
-    uint32_t class_def_idx = 0u;
     for (ClassAccessor accessor : dex_file->GetClasses()) {
       if (unverified_classes.find(accessor.GetClassIdx()) == unverified_classes.end()) {
         if (compiler_only_verifies) {
           // Just update the compiled_classes_ map. The compiler doesn't need to resolve
           // the type.
-          ClassReference ref(dex_file, class_def_idx);
+          ClassReference ref(dex_file, accessor.GetClassDefIndex());
           const ClassStatus existing = ClassStatus::kNotReady;
           ClassStateTable::InsertResult result =
              compiled_classes_.Insert(ref, existing, ClassStatus::kVerified);
@@ -1959,7 +1956,6 @@
                             class_loader,
                             soa.Self());
       }
-      ++class_def_idx;
     }
   }
   return true;
@@ -2700,7 +2696,7 @@
     jobject jclass_loader = context.GetClassLoader();
     ClassReference ref(&dex_file, class_def_index);
     const DexFile::ClassDef& class_def = dex_file.GetClassDef(class_def_index);
-    ClassAccessor accessor(dex_file, class_def);
+    ClassAccessor accessor(dex_file, class_def_index);
     // Skip compiling classes with generic verifier failures since they will still fail at runtime
     if (context.GetCompiler()->GetVerificationResults()->IsClassRejected(ref)) {
       return;
diff --git a/compiler/optimizing/code_generator.cc b/compiler/optimizing/code_generator.cc
index 4791fa3..b3feb78 100644
--- a/compiler/optimizing/code_generator.cc
+++ b/compiler/optimizing/code_generator.cc
@@ -516,7 +516,7 @@
         locations->AddTemp(visitor->GetMethodLocation());
         break;
     }
-  } else {
+  } else if (!invoke->IsInvokePolymorphic()) {
     locations->AddTemp(visitor->GetMethodLocation());
   }
 }
@@ -544,6 +544,7 @@
     case kVirtual:
     case kInterface:
     case kPolymorphic:
+    case kCustom:
       LOG(FATAL) << "Unexpected invoke type: " << invoke->GetInvokeType();
       UNREACHABLE();
   }
@@ -572,6 +573,7 @@
       entrypoint = kQuickInvokeInterfaceTrampolineWithAccessCheck;
       break;
     case kPolymorphic:
+    case kCustom:
       LOG(FATAL) << "Unexpected invoke type: " << invoke->GetInvokeType();
       UNREACHABLE();
   }
@@ -579,11 +581,19 @@
 }
 
 void CodeGenerator::GenerateInvokePolymorphicCall(HInvokePolymorphic* invoke) {
-  MoveConstant(invoke->GetLocations()->GetTemp(0), static_cast<int32_t>(invoke->GetType()));
+  // invoke-polymorphic does not use a temporary to convey any additional information (e.g. a
+  // method index) since it requires multiple info from the instruction (registers A, B, H). Not
+  // using the reservation has no effect on the registers used in the runtime call.
   QuickEntrypointEnum entrypoint = kQuickInvokePolymorphic;
   InvokeRuntime(entrypoint, invoke, invoke->GetDexPc(), nullptr);
 }
 
+void CodeGenerator::GenerateInvokeCustomCall(HInvokeCustom* invoke) {
+  MoveConstant(invoke->GetLocations()->GetTemp(0), invoke->GetCallSiteIndex());
+  QuickEntrypointEnum entrypoint = kQuickInvokeCustom;
+  InvokeRuntime(entrypoint, invoke, invoke->GetDexPc(), nullptr);
+}
+
 void CodeGenerator::CreateUnresolvedFieldLocationSummary(
     HInstruction* field_access,
     DataType::Type field_type,
diff --git a/compiler/optimizing/code_generator.h b/compiler/optimizing/code_generator.h
index a340446..b3c29aa 100644
--- a/compiler/optimizing/code_generator.h
+++ b/compiler/optimizing/code_generator.h
@@ -542,10 +542,13 @@
 
   void GenerateInvokeStaticOrDirectRuntimeCall(
       HInvokeStaticOrDirect* invoke, Location temp, SlowPathCode* slow_path);
+
   void GenerateInvokeUnresolvedRuntimeCall(HInvokeUnresolved* invoke);
 
   void GenerateInvokePolymorphicCall(HInvokePolymorphic* invoke);
 
+  void GenerateInvokeCustomCall(HInvokeCustom* invoke);
+
   void CreateUnresolvedFieldLocationSummary(
       HInstruction* field_access,
       DataType::Type field_type,
diff --git a/compiler/optimizing/code_generator_arm64.cc b/compiler/optimizing/code_generator_arm64.cc
index 6f173e1..5f0533c 100644
--- a/compiler/optimizing/code_generator_arm64.cc
+++ b/compiler/optimizing/code_generator_arm64.cc
@@ -4695,6 +4695,15 @@
   codegen_->MaybeGenerateMarkingRegisterCheck(/* code */ __LINE__);
 }
 
+void LocationsBuilderARM64::VisitInvokeCustom(HInvokeCustom* invoke) {
+  HandleInvoke(invoke);
+}
+
+void InstructionCodeGeneratorARM64::VisitInvokeCustom(HInvokeCustom* invoke) {
+  codegen_->GenerateInvokeCustomCall(invoke);
+  codegen_->MaybeGenerateMarkingRegisterCheck(/* code */ __LINE__);
+}
+
 vixl::aarch64::Label* CodeGeneratorARM64::NewBootImageRelRoPatch(
     uint32_t boot_image_offset,
     vixl::aarch64::Label* adrp_label) {
diff --git a/compiler/optimizing/code_generator_arm_vixl.cc b/compiler/optimizing/code_generator_arm_vixl.cc
index 859e159..91c1315 100644
--- a/compiler/optimizing/code_generator_arm_vixl.cc
+++ b/compiler/optimizing/code_generator_arm_vixl.cc
@@ -3742,6 +3742,15 @@
   codegen_->MaybeGenerateMarkingRegisterCheck(/* code */ 9);
 }
 
+void LocationsBuilderARMVIXL::VisitInvokeCustom(HInvokeCustom* invoke) {
+  HandleInvoke(invoke);
+}
+
+void InstructionCodeGeneratorARMVIXL::VisitInvokeCustom(HInvokeCustom* invoke) {
+  codegen_->GenerateInvokeCustomCall(invoke);
+  codegen_->MaybeGenerateMarkingRegisterCheck(/* code */ 10);
+}
+
 void LocationsBuilderARMVIXL::VisitNeg(HNeg* neg) {
   LocationSummary* locations =
       new (GetGraph()->GetAllocator()) LocationSummary(neg, LocationSummary::kNoCall);
@@ -5493,7 +5502,7 @@
     codegen_->InvokeRuntime(instruction->GetEntrypoint(), instruction, instruction->GetDexPc());
     CheckEntrypointTypes<kQuickAllocObjectWithChecks, void*, mirror::Class*>();
   }
-  codegen_->MaybeGenerateMarkingRegisterCheck(/* code */ 10);
+  codegen_->MaybeGenerateMarkingRegisterCheck(/* code */ 11);
 }
 
 void LocationsBuilderARMVIXL::VisitNewArray(HNewArray* instruction) {
@@ -5513,7 +5522,7 @@
   codegen_->InvokeRuntime(entrypoint, instruction, instruction->GetDexPc());
   CheckEntrypointTypes<kQuickAllocArrayResolved, void*, mirror::Class*, int32_t>();
   DCHECK(!codegen_->IsLeafMethod());
-  codegen_->MaybeGenerateMarkingRegisterCheck(/* code */ 11);
+  codegen_->MaybeGenerateMarkingRegisterCheck(/* code */ 12);
 }
 
 void LocationsBuilderARMVIXL::VisitParameterValue(HParameterValue* instruction) {
@@ -7084,7 +7093,7 @@
     return;
   }
   GenerateSuspendCheck(instruction, nullptr);
-  codegen_->MaybeGenerateMarkingRegisterCheck(/* code */ 12);
+  codegen_->MaybeGenerateMarkingRegisterCheck(/* code */ 13);
 }
 
 void InstructionCodeGeneratorARMVIXL::GenerateSuspendCheck(HSuspendCheck* instruction,
@@ -7437,7 +7446,7 @@
   HLoadClass::LoadKind load_kind = cls->GetLoadKind();
   if (load_kind == HLoadClass::LoadKind::kRuntimeCall) {
     codegen_->GenerateLoadClassRuntimeCall(cls);
-    codegen_->MaybeGenerateMarkingRegisterCheck(/* code */ 13);
+    codegen_->MaybeGenerateMarkingRegisterCheck(/* code */ 14);
     return;
   }
   DCHECK(!cls->NeedsAccessCheck());
@@ -7523,7 +7532,7 @@
     } else {
       __ Bind(slow_path->GetExitLabel());
     }
-    codegen_->MaybeGenerateMarkingRegisterCheck(/* code */ 14);
+    codegen_->MaybeGenerateMarkingRegisterCheck(/* code */ 15);
   }
 }
 
@@ -7732,7 +7741,7 @@
       codegen_->AddSlowPath(slow_path);
       __ CompareAndBranchIfZero(out, slow_path->GetEntryLabel());
       __ Bind(slow_path->GetExitLabel());
-      codegen_->MaybeGenerateMarkingRegisterCheck(/* code */ 15);
+      codegen_->MaybeGenerateMarkingRegisterCheck(/* code */ 16);
       return;
     }
     case HLoadString::LoadKind::kJitTableAddress: {
@@ -7754,7 +7763,7 @@
   __ Mov(calling_convention.GetRegisterAt(0), load->GetStringIndex().index_);
   codegen_->InvokeRuntime(kQuickResolveString, load, load->GetDexPc());
   CheckEntrypointTypes<kQuickResolveString, void*, uint32_t>();
-  codegen_->MaybeGenerateMarkingRegisterCheck(/* code */ 16);
+  codegen_->MaybeGenerateMarkingRegisterCheck(/* code */ 17);
 }
 
 static int32_t GetExceptionTlsOffset() {
@@ -8384,7 +8393,7 @@
   } else {
     CheckEntrypointTypes<kQuickUnlockObject, void, mirror::Object*>();
   }
-  codegen_->MaybeGenerateMarkingRegisterCheck(/* code */ 17);
+  codegen_->MaybeGenerateMarkingRegisterCheck(/* code */ 18);
 }
 
 void LocationsBuilderARMVIXL::VisitAnd(HAnd* instruction) {
@@ -8883,7 +8892,7 @@
     // Note that GC roots are not affected by heap poisoning, thus we
     // do not have to unpoison `root_reg` here.
   }
-  MaybeGenerateMarkingRegisterCheck(/* code */ 18);
+  MaybeGenerateMarkingRegisterCheck(/* code */ 19);
 }
 
 void CodeGeneratorARMVIXL::GenerateFieldLoadWithBakerReadBarrier(HInstruction* instruction,
@@ -8963,7 +8972,7 @@
                 narrow ? BAKER_MARK_INTROSPECTION_FIELD_LDR_NARROW_OFFSET
                        : BAKER_MARK_INTROSPECTION_FIELD_LDR_WIDE_OFFSET);
     }
-    MaybeGenerateMarkingRegisterCheck(/* code */ 19, /* temp_loc */ LocationFrom(ip));
+    MaybeGenerateMarkingRegisterCheck(/* code */ 20, /* temp_loc */ LocationFrom(ip));
     return;
   }
 
@@ -9041,7 +9050,7 @@
       DCHECK_EQ(old_offset - GetVIXLAssembler()->GetBuffer()->GetCursorOffset(),
                 BAKER_MARK_INTROSPECTION_ARRAY_LDR_OFFSET);
     }
-    MaybeGenerateMarkingRegisterCheck(/* code */ 20, /* temp_loc */ LocationFrom(ip));
+    MaybeGenerateMarkingRegisterCheck(/* code */ 21, /* temp_loc */ LocationFrom(ip));
     return;
   }
 
@@ -9095,7 +9104,7 @@
   // Fast path: the GC is not marking: just load the reference.
   GenerateRawReferenceLoad(instruction, ref, obj, offset, index, scale_factor, needs_null_check);
   __ Bind(slow_path->GetExitLabel());
-  MaybeGenerateMarkingRegisterCheck(/* code */ 21);
+  MaybeGenerateMarkingRegisterCheck(/* code */ 22);
 }
 
 void CodeGeneratorARMVIXL::UpdateReferenceFieldWithBakerReadBarrier(HInstruction* instruction,
@@ -9150,7 +9159,7 @@
   // Fast path: the GC is not marking: nothing to do (the field is
   // up-to-date, and we don't need to load the reference).
   __ Bind(slow_path->GetExitLabel());
-  MaybeGenerateMarkingRegisterCheck(/* code */ 22);
+  MaybeGenerateMarkingRegisterCheck(/* code */ 23);
 }
 
 void CodeGeneratorARMVIXL::GenerateRawReferenceLoad(HInstruction* instruction,
diff --git a/compiler/optimizing/code_generator_mips.cc b/compiler/optimizing/code_generator_mips.cc
index 8be84a1..507db36 100644
--- a/compiler/optimizing/code_generator_mips.cc
+++ b/compiler/optimizing/code_generator_mips.cc
@@ -7795,6 +7795,14 @@
   codegen_->GenerateInvokePolymorphicCall(invoke);
 }
 
+void LocationsBuilderMIPS::VisitInvokeCustom(HInvokeCustom* invoke) {
+  HandleInvoke(invoke);
+}
+
+void InstructionCodeGeneratorMIPS::VisitInvokeCustom(HInvokeCustom* invoke) {
+  codegen_->GenerateInvokeCustomCall(invoke);
+}
+
 static bool TryGenerateIntrinsicCode(HInvoke* invoke, CodeGeneratorMIPS* codegen) {
   if (invoke->GetLocations()->Intrinsified()) {
     IntrinsicCodeGeneratorMIPS intrinsic(codegen);
diff --git a/compiler/optimizing/code_generator_mips64.cc b/compiler/optimizing/code_generator_mips64.cc
index cd9e0e5..08a6512 100644
--- a/compiler/optimizing/code_generator_mips64.cc
+++ b/compiler/optimizing/code_generator_mips64.cc
@@ -5908,6 +5908,14 @@
   codegen_->GenerateInvokePolymorphicCall(invoke);
 }
 
+void LocationsBuilderMIPS64::VisitInvokeCustom(HInvokeCustom* invoke) {
+  HandleInvoke(invoke);
+}
+
+void InstructionCodeGeneratorMIPS64::VisitInvokeCustom(HInvokeCustom* invoke) {
+  codegen_->GenerateInvokeCustomCall(invoke);
+}
+
 static bool TryGenerateIntrinsicCode(HInvoke* invoke, CodeGeneratorMIPS64* codegen) {
   if (invoke->GetLocations()->Intrinsified()) {
     IntrinsicCodeGeneratorMIPS64 intrinsic(codegen);
diff --git a/compiler/optimizing/code_generator_x86.cc b/compiler/optimizing/code_generator_x86.cc
index 9e31538..9f42ac7 100644
--- a/compiler/optimizing/code_generator_x86.cc
+++ b/compiler/optimizing/code_generator_x86.cc
@@ -2311,6 +2311,14 @@
   codegen_->GenerateInvokePolymorphicCall(invoke);
 }
 
+void LocationsBuilderX86::VisitInvokeCustom(HInvokeCustom* invoke) {
+  HandleInvoke(invoke);
+}
+
+void InstructionCodeGeneratorX86::VisitInvokeCustom(HInvokeCustom* invoke) {
+  codegen_->GenerateInvokeCustomCall(invoke);
+}
+
 void LocationsBuilderX86::VisitNeg(HNeg* neg) {
   LocationSummary* locations =
       new (GetGraph()->GetAllocator()) LocationSummary(neg, LocationSummary::kNoCall);
diff --git a/compiler/optimizing/code_generator_x86_64.cc b/compiler/optimizing/code_generator_x86_64.cc
index f739704..05194b1 100644
--- a/compiler/optimizing/code_generator_x86_64.cc
+++ b/compiler/optimizing/code_generator_x86_64.cc
@@ -2501,6 +2501,14 @@
   codegen_->GenerateInvokePolymorphicCall(invoke);
 }
 
+void LocationsBuilderX86_64::VisitInvokeCustom(HInvokeCustom* invoke) {
+  HandleInvoke(invoke);
+}
+
+void InstructionCodeGeneratorX86_64::VisitInvokeCustom(HInvokeCustom* invoke) {
+  codegen_->GenerateInvokeCustomCall(invoke);
+}
+
 void LocationsBuilderX86_64::VisitNeg(HNeg* neg) {
   LocationSummary* locations =
       new (GetGraph()->GetAllocator()) LocationSummary(neg, LocationSummary::kNoCall);
diff --git a/compiler/optimizing/inliner.cc b/compiler/optimizing/inliner.cc
index 6900cd8..7dd756e 100644
--- a/compiler/optimizing/inliner.cc
+++ b/compiler/optimizing/inliner.cc
@@ -460,9 +460,10 @@
 
 bool HInliner::TryInline(HInvoke* invoke_instruction) {
   if (invoke_instruction->IsInvokeUnresolved() ||
-      invoke_instruction->IsInvokePolymorphic()) {
-    return false;  // Don't bother to move further if we know the method is unresolved or an
-                   // invoke-polymorphic.
+      invoke_instruction->IsInvokePolymorphic() ||
+      invoke_instruction->IsInvokeCustom()) {
+    return false;  // Don't bother to move further if we know the method is unresolved or the
+                   // invocation is polymorphic (invoke-{polymorphic,custom}).
   }
 
   ScopedObjectAccess soa(Thread::Current());
diff --git a/compiler/optimizing/instruction_builder.cc b/compiler/optimizing/instruction_builder.cc
index 24dc2ee..8a347df 100644
--- a/compiler/optimizing/instruction_builder.cc
+++ b/compiler/optimizing/instruction_builder.cc
@@ -449,11 +449,7 @@
       target_method,
       HInvokeStaticOrDirect::ClinitCheckRequirement::kNone);
   RangeInstructionOperands operands(graph_->GetNumberOfVRegs() - in_vregs, in_vregs);
-  HandleInvoke(invoke,
-               operands,
-               dex_file_->GetMethodShorty(method_idx),
-               /* clinit_check */ nullptr,
-               /* is_unresolved */ false);
+  HandleInvoke(invoke, operands, dex_file_->GetMethodShorty(method_idx), /* is_unresolved */ false);
 
   // Add the return instruction.
   if (return_type_ == DataType::Type::kVoid) {
@@ -916,11 +912,11 @@
                                       uint32_t method_idx,
                                       const InstructionOperands& operands) {
   InvokeType invoke_type = GetInvokeTypeFromOpCode(instruction.Opcode());
-  const char* descriptor = dex_file_->GetMethodShorty(method_idx);
-  DataType::Type return_type = DataType::FromShorty(descriptor[0]);
+  const char* shorty = dex_file_->GetMethodShorty(method_idx);
+  DataType::Type return_type = DataType::FromShorty(shorty[0]);
 
   // Remove the return type from the 'proto'.
-  size_t number_of_arguments = strlen(descriptor) - 1;
+  size_t number_of_arguments = strlen(shorty) - 1;
   if (invoke_type != kStatic) {  // instance call
     // One extra argument for 'this'.
     number_of_arguments++;
@@ -937,11 +933,7 @@
                                                          dex_pc,
                                                          method_idx,
                                                          invoke_type);
-    return HandleInvoke(invoke,
-                        operands,
-                        descriptor,
-                        nullptr /* clinit_check */,
-                        true /* is_unresolved */);
+    return HandleInvoke(invoke, operands, shorty, /* is_unresolved */ true);
   }
 
   // Replace calls to String.<init> with StringFactory.
@@ -968,7 +960,7 @@
         invoke_type,
         target_method,
         HInvokeStaticOrDirect::ClinitCheckRequirement::kImplicit);
-    return HandleStringInit(invoke, operands, descriptor);
+    return HandleStringInit(invoke, operands, shorty);
   }
 
   // Potential class initialization check, in the case of a static method call.
@@ -1028,29 +1020,39 @@
                                                resolved_method,
                                                ImTable::GetImtIndex(resolved_method));
   }
-
-  return HandleInvoke(invoke, operands, descriptor, clinit_check, false /* is_unresolved */);
+  return HandleInvoke(invoke, operands, shorty, /* is_unresolved */ false, clinit_check);
 }
 
-bool HInstructionBuilder::BuildInvokePolymorphic(const Instruction& instruction ATTRIBUTE_UNUSED,
-                                                 uint32_t dex_pc,
+bool HInstructionBuilder::BuildInvokePolymorphic(uint32_t dex_pc,
                                                  uint32_t method_idx,
                                                  dex::ProtoIndex proto_idx,
                                                  const InstructionOperands& operands) {
-  const char* descriptor = dex_file_->GetShorty(proto_idx);
-  DCHECK_EQ(1 + ArtMethod::NumArgRegisters(descriptor), operands.GetNumberOfOperands());
-  DataType::Type return_type = DataType::FromShorty(descriptor[0]);
-  size_t number_of_arguments = strlen(descriptor);
+  const char* shorty = dex_file_->GetShorty(proto_idx);
+  DCHECK_EQ(1 + ArtMethod::NumArgRegisters(shorty), operands.GetNumberOfOperands());
+  DataType::Type return_type = DataType::FromShorty(shorty[0]);
+  size_t number_of_arguments = strlen(shorty);
   HInvoke* invoke = new (allocator_) HInvokePolymorphic(allocator_,
                                                         number_of_arguments,
                                                         return_type,
                                                         dex_pc,
                                                         method_idx);
-  return HandleInvoke(invoke,
-                      operands,
-                      descriptor,
-                      nullptr /* clinit_check */,
-                      false /* is_unresolved */);
+  return HandleInvoke(invoke, operands, shorty, /* is_unresolved */ false);
+}
+
+
+bool HInstructionBuilder::BuildInvokeCustom(uint32_t dex_pc,
+                                            uint32_t call_site_idx,
+                                            const InstructionOperands& operands) {
+  dex::ProtoIndex proto_idx = dex_file_->GetProtoIndexForCallSite(call_site_idx);
+  const char* shorty = dex_file_->GetShorty(proto_idx);
+  DataType::Type return_type = DataType::FromShorty(shorty[0]);
+  size_t number_of_arguments = strlen(shorty) - 1;
+  HInvoke* invoke = new (allocator_) HInvokeCustom(allocator_,
+                                                   number_of_arguments,
+                                                   call_site_idx,
+                                                   return_type,
+                                                   dex_pc);
+  return HandleInvoke(invoke, operands, shorty, /* is_unresolved */ false);
 }
 
 HNewInstance* HInstructionBuilder::BuildNewInstance(dex::TypeIndex type_index, uint32_t dex_pc) {
@@ -1197,10 +1199,10 @@
 
 bool HInstructionBuilder::SetupInvokeArguments(HInvoke* invoke,
                                                const InstructionOperands& operands,
-                                               const char* descriptor,
+                                               const char* shorty,
                                                size_t start_index,
                                                size_t* argument_index) {
-  uint32_t descriptor_index = 1;  // Skip the return type.
+  uint32_t shorty_index = 1;  // Skip the return type.
   const size_t number_of_operands = operands.GetNumberOfOperands();
   for (size_t i = start_index;
        // Make sure we don't go over the expected arguments or over the number of
@@ -1208,7 +1210,7 @@
        // it hasn't been properly checked.
        (i < number_of_operands) && (*argument_index < invoke->GetNumberOfArguments());
        i++, (*argument_index)++) {
-    DataType::Type type = DataType::FromShorty(descriptor[descriptor_index++]);
+    DataType::Type type = DataType::FromShorty(shorty[shorty_index++]);
     bool is_wide = (type == DataType::Type::kInt64) || (type == DataType::Type::kFloat64);
     if (is_wide && ((i + 1 == number_of_operands) ||
                     (operands.GetOperand(i) + 1 != operands.GetOperand(i + 1)))) {
@@ -1250,9 +1252,9 @@
 
 bool HInstructionBuilder::HandleInvoke(HInvoke* invoke,
                                        const InstructionOperands& operands,
-                                       const char* descriptor,
-                                       HClinitCheck* clinit_check,
-                                       bool is_unresolved) {
+                                       const char* shorty,
+                                       bool is_unresolved,
+                                       HClinitCheck* clinit_check) {
   DCHECK(!invoke->IsInvokeStaticOrDirect() || !invoke->AsInvokeStaticOrDirect()->IsStringInit());
 
   size_t start_index = 0;
@@ -1267,7 +1269,7 @@
     argument_index = 1;
   }
 
-  if (!SetupInvokeArguments(invoke, operands, descriptor, start_index, &argument_index)) {
+  if (!SetupInvokeArguments(invoke, operands, shorty, start_index, &argument_index)) {
     return false;
   }
 
@@ -1288,13 +1290,13 @@
 
 bool HInstructionBuilder::HandleStringInit(HInvoke* invoke,
                                            const InstructionOperands& operands,
-                                           const char* descriptor) {
+                                           const char* shorty) {
   DCHECK(invoke->IsInvokeStaticOrDirect());
   DCHECK(invoke->AsInvokeStaticOrDirect()->IsStringInit());
 
   size_t start_index = 1;
   size_t argument_index = 0;
-  if (!SetupInvokeArguments(invoke, operands, descriptor, start_index, &argument_index)) {
+  if (!SetupInvokeArguments(invoke, operands, shorty, start_index, &argument_index)) {
     return false;
   }
 
@@ -1313,7 +1315,8 @@
     // The only reason a HPhi can flow in a String.<init> is when there is an
     // irreducible loop, which will create HPhi for all dex registers at loop entry.
     DCHECK(arg_this->IsPhi());
-    DCHECK(graph_->HasIrreducibleLoops());
+    // TODO(b/109666561): Re-enable.
+    // DCHECK(graph_->HasIrreducibleLoops());
     // Don't bother compiling a method in that situation. While we could look at all
     // phis related to the HNewInstance, it's not worth the trouble.
     MaybeRecordStat(compilation_stats_,
@@ -2144,14 +2147,28 @@
       uint32_t args[5];
       uint32_t number_of_vreg_arguments = instruction.GetVarArgs(args);
       VarArgsInstructionOperands operands(args, number_of_vreg_arguments);
-      return BuildInvokePolymorphic(instruction, dex_pc, method_idx, proto_idx, operands);
+      return BuildInvokePolymorphic(dex_pc, method_idx, proto_idx, operands);
     }
 
     case Instruction::INVOKE_POLYMORPHIC_RANGE: {
       uint16_t method_idx = instruction.VRegB_4rcc();
       dex::ProtoIndex proto_idx(instruction.VRegH_4rcc());
       RangeInstructionOperands operands(instruction.VRegC_4rcc(), instruction.VRegA_4rcc());
-      return BuildInvokePolymorphic(instruction, dex_pc, method_idx, proto_idx, operands);
+      return BuildInvokePolymorphic(dex_pc, method_idx, proto_idx, operands);
+    }
+
+    case Instruction::INVOKE_CUSTOM: {
+      uint16_t call_site_idx = instruction.VRegB_35c();
+      uint32_t args[5];
+      uint32_t number_of_vreg_arguments = instruction.GetVarArgs(args);
+      VarArgsInstructionOperands operands(args, number_of_vreg_arguments);
+      return BuildInvokeCustom(dex_pc, call_site_idx, operands);
+    }
+
+    case Instruction::INVOKE_CUSTOM_RANGE: {
+      uint16_t call_site_idx = instruction.VRegB_3rc();
+      RangeInstructionOperands operands(instruction.VRegC_3rc(), instruction.VRegA_3rc());
+      return BuildInvokeCustom(dex_pc, call_site_idx, operands);
     }
 
     case Instruction::NEG_INT: {
@@ -2933,7 +2950,21 @@
       break;
     }
 
-    default:
+    case Instruction::UNUSED_3E:
+    case Instruction::UNUSED_3F:
+    case Instruction::UNUSED_40:
+    case Instruction::UNUSED_41:
+    case Instruction::UNUSED_42:
+    case Instruction::UNUSED_43:
+    case Instruction::UNUSED_79:
+    case Instruction::UNUSED_7A:
+    case Instruction::UNUSED_F3:
+    case Instruction::UNUSED_F4:
+    case Instruction::UNUSED_F5:
+    case Instruction::UNUSED_F6:
+    case Instruction::UNUSED_F7:
+    case Instruction::UNUSED_F8:
+    case Instruction::UNUSED_F9: {
       VLOG(compiler) << "Did not compile "
                      << dex_file_->PrettyMethod(dex_compilation_unit_->GetDexMethodIndex())
                      << " because of unhandled instruction "
@@ -2941,6 +2972,7 @@
       MaybeRecordStat(compilation_stats_,
                       MethodCompilationStat::kNotCompiledUnhandledInstruction);
       return false;
+    }
   }
   return true;
 }  // NOLINT(readability/fn_size)
diff --git a/compiler/optimizing/instruction_builder.h b/compiler/optimizing/instruction_builder.h
index 2218a69..af7092a 100644
--- a/compiler/optimizing/instruction_builder.h
+++ b/compiler/optimizing/instruction_builder.h
@@ -173,12 +173,17 @@
 
   // Builds an invocation node for invoke-polymorphic and returns whether the
   // instruction is supported.
-  bool BuildInvokePolymorphic(const Instruction& instruction,
-                              uint32_t dex_pc,
+  bool BuildInvokePolymorphic(uint32_t dex_pc,
                               uint32_t method_idx,
                               dex::ProtoIndex proto_idx,
                               const InstructionOperands& operands);
 
+  // Builds an invocation node for invoke-custom and returns whether the
+  // instruction is supported.
+  bool BuildInvokeCustom(uint32_t dex_pc,
+                         uint32_t call_site_idx,
+                         const InstructionOperands& operands);
+
   // Builds a new array node and the instructions that fill it.
   HNewArray* BuildFilledNewArray(uint32_t dex_pc,
                                  dex::TypeIndex type_index,
@@ -253,19 +258,19 @@
 
   bool SetupInvokeArguments(HInvoke* invoke,
                             const InstructionOperands& operands,
-                            const char* descriptor,
+                            const char* shorty,
                             size_t start_index,
                             size_t* argument_index);
 
   bool HandleInvoke(HInvoke* invoke,
                     const InstructionOperands& operands,
-                    const char* descriptor,
-                    HClinitCheck* clinit_check,
-                    bool is_unresolved);
+                    const char* shorty,
+                    bool is_unresolved,
+                    HClinitCheck* clinit_check = nullptr);
 
   bool HandleStringInit(HInvoke* invoke,
                         const InstructionOperands& operands,
-                        const char* descriptor);
+                        const char* shorty);
   void HandleStringInitResult(HInvokeStaticOrDirect* invoke);
 
   HClinitCheck* ProcessClinitCheckForInvoke(
diff --git a/compiler/optimizing/intrinsics.cc b/compiler/optimizing/intrinsics.cc
index 056f533..f0c91f3 100644
--- a/compiler/optimizing/intrinsics.cc
+++ b/compiler/optimizing/intrinsics.cc
@@ -142,6 +142,7 @@
     case kSuper:
     case kInterface:
     case kPolymorphic:
+    case kCustom:
       return false;
   }
   LOG(FATAL) << "Unknown intrinsic invoke type: " << intrinsic_type;
@@ -272,34 +273,33 @@
   ClassLinker* class_linker = runtime->GetClassLinker();
   gc::Heap* heap = runtime->GetHeap();
   IntegerValueOfInfo info;
-  info.integer_cache =
-      class_linker->FindSystemClass(self, "Ljava/lang/Integer$IntegerCache;").Ptr();
-  if (info.integer_cache == nullptr) {
-    self->ClearException();
+  info.integer_cache = class_linker->LookupClass(self,
+                                                 "Ljava/lang/Integer$IntegerCache;",
+                                                 /* class_loader */ nullptr).Ptr();
+  if (info.integer_cache == nullptr || !info.integer_cache->IsInitialized()) {
+    // Optimization only works if the class is initialized.
     return info;
   }
-  if (!heap->ObjectIsInBootImageSpace(info.integer_cache) || !info.integer_cache->IsInitialized()) {
-    // Optimization only works if the class is initialized and in the boot image.
+  if (!heap->ObjectIsInBootImageSpace(info.integer_cache)) {
+    // Optimization only works if the class is in the boot image.
+    // TODO: Implement the intrinsic for boot image compilation.
     return info;
   }
-  info.integer = class_linker->FindSystemClass(self, "Ljava/lang/Integer;").Ptr();
-  if (info.integer == nullptr) {
-    self->ClearException();
-    return info;
-  }
-  if (!heap->ObjectIsInBootImageSpace(info.integer) || !info.integer->IsInitialized()) {
-    // Optimization only works if the class is initialized and in the boot image.
+  info.integer =
+      class_linker->LookupClass(self, "Ljava/lang/Integer;", /* class_loader */ nullptr).Ptr();
+  DCHECK(info.integer != nullptr);
+  DCHECK(info.integer->IsInitialized());  // Must be initialized since IntegerCache is initialized.
+  if (!heap->ObjectIsInBootImageSpace(info.integer)) {
+    // Optimization only works if the class is in the boot image.
     return info;
   }
 
   ArtField* field = info.integer_cache->FindDeclaredStaticField("cache", "[Ljava/lang/Integer;");
-  if (field == nullptr) {
-    return info;
-  }
+  CHECK(field != nullptr);
   info.cache = static_cast<mirror::ObjectArray<mirror::Object>*>(
       field->GetObject(info.integer_cache).Ptr());
   if (info.cache == nullptr) {
-    return info;
+    return info;  // Did someone mess up the IntegerCache using reflection?
   }
 
   if (!heap->ObjectIsInBootImageSpace(info.cache)) {
@@ -308,21 +308,15 @@
   }
 
   field = info.integer->FindDeclaredInstanceField("value", "I");
-  if (field == nullptr) {
-    return info;
-  }
+  CHECK(field != nullptr);
   info.value_offset = field->GetOffset().Int32Value();
 
   field = info.integer_cache->FindDeclaredStaticField("low", "I");
-  if (field == nullptr) {
-    return info;
-  }
+  CHECK(field != nullptr);
   info.low = field->GetInt(info.integer_cache);
 
   field = info.integer_cache->FindDeclaredStaticField("high", "I");
-  if (field == nullptr) {
-    return info;
-  }
+  CHECK(field != nullptr);
   info.high = field->GetInt(info.integer_cache);
 
   DCHECK_EQ(info.cache->GetLength(), info.high - info.low + 1);
diff --git a/compiler/optimizing/nodes.h b/compiler/optimizing/nodes.h
index 3fd5b6b..2037879 100644
--- a/compiler/optimizing/nodes.h
+++ b/compiler/optimizing/nodes.h
@@ -1380,6 +1380,7 @@
   M(InvokeStaticOrDirect, Invoke)                                       \
   M(InvokeVirtual, Invoke)                                              \
   M(InvokePolymorphic, Invoke)                                          \
+  M(InvokeCustom, Invoke)                                               \
   M(LessThan, Condition)                                                \
   M(LessThanOrEqual, Condition)                                         \
   M(LoadClass, Instruction)                                             \
@@ -4382,6 +4383,38 @@
   DEFAULT_COPY_CONSTRUCTOR(InvokePolymorphic);
 };
 
+class HInvokeCustom FINAL : public HInvoke {
+ public:
+  HInvokeCustom(ArenaAllocator* allocator,
+                uint32_t number_of_arguments,
+                uint32_t call_site_index,
+                DataType::Type return_type,
+                uint32_t dex_pc)
+      : HInvoke(kInvokeCustom,
+                allocator,
+                number_of_arguments,
+                /* number_of_other_inputs */ 0u,
+                return_type,
+                dex_pc,
+                /* dex_method_index */ dex::kDexNoIndex,
+                /* resolved_method */ nullptr,
+                kStatic),
+      call_site_index_(call_site_index) {
+  }
+
+  uint32_t GetCallSiteIndex() const { return call_site_index_; }
+
+  bool IsClonable() const OVERRIDE { return true; }
+
+  DECLARE_INSTRUCTION(InvokeCustom);
+
+ protected:
+  DEFAULT_COPY_CONSTRUCTOR(InvokeCustom);
+
+ private:
+  uint32_t call_site_index_;
+};
+
 class HInvokeStaticOrDirect FINAL : public HInvoke {
  public:
   // Requirements of this method call regarding the class
@@ -6510,9 +6543,9 @@
 class HLoadMethodHandle FINAL : public HInstruction {
  public:
   HLoadMethodHandle(HCurrentMethod* current_method,
-                  uint16_t method_handle_idx,
-                  const DexFile& dex_file,
-                  uint32_t dex_pc)
+                    uint16_t method_handle_idx,
+                    const DexFile& dex_file,
+                    uint32_t dex_pc)
       : HInstruction(kLoadMethodHandle,
                      DataType::Type::kReference,
                      SideEffectsForArchRuntimeCalls(),
diff --git a/compiler/optimizing/stack_map_stream.cc b/compiler/optimizing/stack_map_stream.cc
index d80e2fc..094b75d 100644
--- a/compiler/optimizing/stack_map_stream.cc
+++ b/compiler/optimizing/stack_map_stream.cc
@@ -46,6 +46,13 @@
                                         uint8_t inlining_depth) {
   DCHECK(!in_stack_map_) << "Mismatched Begin/End calls";
   in_stack_map_ = true;
+  // num_dex_registers_ is the constant per-method number of registers.
+  // However we initially don't know what the value is, so lazily initialize it.
+  if (num_dex_registers_ == 0) {
+    num_dex_registers_ = num_dex_registers;
+  } else if (num_dex_registers > 0) {
+    DCHECK_EQ(num_dex_registers_, num_dex_registers) << "Inconsistent register count";
+  }
 
   current_stack_map_ = StackMapEntry {
     .packed_native_pc = StackMap::PackNativePc(native_pc_offset, instruction_set_),
@@ -85,7 +92,6 @@
       }
       CHECK_EQ(stack_map.HasInlineInfo(), (inlining_depth != 0));
       CHECK_EQ(code_info.GetInlineDepthOf(stack_map), inlining_depth);
-      CHECK_EQ(stack_map.HasDexRegisterMap(), (num_dex_registers != 0));
     });
   }
 }
@@ -102,16 +108,14 @@
         inline_infos_.Dedup(current_inline_infos_.data(), current_inline_infos_.size());
   }
 
+  // Generate delta-compressed dex register map.
+  CreateDexRegisterMap();
+
   stack_maps_.Add(current_stack_map_);
 }
 
 void StackMapStream::AddDexRegisterEntry(DexRegisterLocation::Kind kind, int32_t value) {
   current_dex_registers_.push_back(DexRegisterLocation(kind, value));
-
-  // We have collected all the dex registers for StackMap/InlineInfo - create the map.
-  if (current_dex_registers_.size() == expected_num_dex_registers_) {
-    CreateDexRegisterMap();
-  }
 }
 
 void StackMapStream::AddInvoke(InvokeType invoke_type, uint32_t dex_method_index) {
@@ -129,7 +133,7 @@
       CHECK_EQ(invoke_info.GetNativePcOffset(instruction_set_),
                StackMap::UnpackNativePc(packed_native_pc, instruction_set_));
       CHECK_EQ(invoke_info.GetInvokeType(), invoke_type);
-      CHECK_EQ(method_infos_[invoke_info.GetMethodIndexIdx()], dex_method_index);
+      CHECK_EQ(method_infos_[invoke_info.GetMethodInfoIndex()], dex_method_index);
     });
   }
 }
@@ -142,14 +146,15 @@
   in_inline_info_ = true;
   DCHECK_EQ(expected_num_dex_registers_, current_dex_registers_.size());
 
+  expected_num_dex_registers_ += num_dex_registers;
+
   InlineInfoEntry entry = {
     .is_last = InlineInfo::kMore,
     .dex_pc = dex_pc,
     .method_info_index = kNoValue,
     .art_method_hi = kNoValue,
     .art_method_lo = kNoValue,
-    .dex_register_mask_index = kNoValue,
-    .dex_register_map_index = kNoValue,
+    .num_dex_registers = static_cast<uint32_t>(expected_num_dex_registers_),
   };
   if (EncodeArtMethodInInlineInfo(method)) {
     entry.art_method_hi = High32Bits(reinterpret_cast<uintptr_t>(method));
@@ -164,9 +169,6 @@
   }
   current_inline_infos_.push_back(entry);
 
-  current_dex_registers_.clear();
-  expected_num_dex_registers_ = num_dex_registers;
-
   if (kVerifyStackMaps) {
     size_t stack_map_index = stack_maps_.size();
     size_t depth = current_inline_infos_.size() - 1;
@@ -179,10 +181,9 @@
       if (encode_art_method) {
         CHECK_EQ(inline_info.GetArtMethod(), method);
       } else {
-        CHECK_EQ(method_infos_[inline_info.GetMethodIndexIdx()],
+        CHECK_EQ(method_infos_[inline_info.GetMethodInfoIndex()],
                  method->GetDexMethodIndexUnchecked());
       }
-      CHECK_EQ(inline_info.HasDexRegisterMap(), (num_dex_registers != 0));
     });
   }
 }
@@ -193,56 +194,68 @@
   DCHECK_EQ(expected_num_dex_registers_, current_dex_registers_.size());
 }
 
-// Create dex register map (bitmap + indices + catalogue entries)
-// based on the currently accumulated list of DexRegisterLocations.
+// Create delta-compressed dex register map based on the current list of DexRegisterLocations.
+// All dex registers for a stack map are concatenated - inlined registers are just appended.
 void StackMapStream::CreateDexRegisterMap() {
-  // Create mask and map based on current registers.
+  // These are fields rather than local variables so that we can reuse the reserved memory.
   temp_dex_register_mask_.ClearAllBits();
   temp_dex_register_map_.clear();
+
+  // Ensure that the arrays that hold previous state are big enough to be safely indexed below.
+  if (previous_dex_registers_.size() < current_dex_registers_.size()) {
+    previous_dex_registers_.resize(current_dex_registers_.size(), DexRegisterLocation::None());
+    dex_register_timestamp_.resize(current_dex_registers_.size(), 0u);
+  }
+
+  // Set bit in the mask for each register that has been changed since the previous stack map.
+  // Modified registers are stored in the catalogue and the catalogue index added to the list.
   for (size_t i = 0; i < current_dex_registers_.size(); i++) {
     DexRegisterLocation reg = current_dex_registers_[i];
-    if (reg.IsLive()) {
-      DexRegisterEntry entry = DexRegisterEntry {
+    // Distance is difference between this index and the index of last modification.
+    uint32_t distance = stack_maps_.size() - dex_register_timestamp_[i];
+    if (previous_dex_registers_[i] != reg || distance > kMaxDexRegisterMapSearchDistance) {
+      DexRegisterEntry entry = DexRegisterEntry{
         .kind = static_cast<uint32_t>(reg.GetKind()),
         .packed_value = DexRegisterInfo::PackValue(reg.GetKind(), reg.GetValue()),
       };
+      uint32_t index = reg.IsLive() ? dex_register_catalog_.Dedup(&entry) : kNoValue;
       temp_dex_register_mask_.SetBit(i);
-      temp_dex_register_map_.push_back(dex_register_catalog_.Dedup(&entry));
+      temp_dex_register_map_.push_back(index);
+      previous_dex_registers_[i] = reg;
+      dex_register_timestamp_[i] = stack_maps_.size();
     }
   }
 
-  // Set the mask and map for the current StackMap/InlineInfo.
-  uint32_t mask_index = StackMap::kNoValue;  // Represents mask with all zero bits.
+  // Set the mask and map for the current StackMap (which includes inlined registers).
   if (temp_dex_register_mask_.GetNumberOfBits() != 0) {
-    mask_index = dex_register_masks_.Dedup(temp_dex_register_mask_.GetRawStorage(),
-                                           temp_dex_register_mask_.GetNumberOfBits());
+    current_stack_map_.dex_register_mask_index =
+        dex_register_masks_.Dedup(temp_dex_register_mask_.GetRawStorage(),
+                                  temp_dex_register_mask_.GetNumberOfBits());
   }
-  uint32_t map_index = dex_register_maps_.Dedup(temp_dex_register_map_.data(),
-                                                temp_dex_register_map_.size());
-  if (!current_inline_infos_.empty()) {
-    current_inline_infos_.back().dex_register_mask_index = mask_index;
-    current_inline_infos_.back().dex_register_map_index = map_index;
-  } else {
-    current_stack_map_.dex_register_mask_index = mask_index;
-    current_stack_map_.dex_register_map_index = map_index;
+  if (!current_dex_registers_.empty()) {
+    current_stack_map_.dex_register_map_index =
+        dex_register_maps_.Dedup(temp_dex_register_map_.data(),
+                                 temp_dex_register_map_.size());
   }
 
   if (kVerifyStackMaps) {
     size_t stack_map_index = stack_maps_.size();
-    int32_t depth = current_inline_infos_.size() - 1;
+    uint32_t depth = current_inline_infos_.size();
     // We need to make copy of the current registers for later (when the check is run).
-    auto expected_dex_registers = std::make_shared<std::vector<DexRegisterLocation>>(
+    auto expected_dex_registers = std::make_shared<dchecked_vector<DexRegisterLocation>>(
         current_dex_registers_.begin(), current_dex_registers_.end());
     dchecks_.emplace_back([=](const CodeInfo& code_info) {
       StackMap stack_map = code_info.GetStackMapAt(stack_map_index);
-      size_t num_dex_registers = expected_dex_registers->size();
-      DexRegisterMap map = (depth == -1)
-        ? code_info.GetDexRegisterMapOf(stack_map, num_dex_registers)
-        : code_info.GetDexRegisterMapAtDepth(depth, stack_map, num_dex_registers);
-      CHECK_EQ(map.size(), num_dex_registers);
-      for (size_t r = 0; r < num_dex_registers; r++) {
-        CHECK_EQ(expected_dex_registers->at(r), map.Get(r));
+      uint32_t expected_reg = 0;
+      for (DexRegisterLocation reg : code_info.GetDexRegisterMapOf(stack_map)) {
+        CHECK_EQ((*expected_dex_registers)[expected_reg++], reg);
       }
+      for (uint32_t d = 0; d < depth; d++) {
+        for (DexRegisterLocation reg : code_info.GetDexRegisterMapAtDepth(d, stack_map)) {
+          CHECK_EQ((*expected_dex_registers)[expected_reg++], reg);
+        }
+      }
+      CHECK_EQ(expected_reg, expected_dex_registers->size());
     });
   }
 }
@@ -290,6 +303,7 @@
   dex_register_masks_.Encode(&out_, &bit_offset);
   dex_register_maps_.Encode(&out_, &bit_offset);
   dex_register_catalog_.Encode(&out_, &bit_offset);
+  EncodeVarintBits(&out_, &bit_offset, num_dex_registers_);
 
   return UnsignedLeb128Size(out_.size()) +  out_.size();
 }
diff --git a/compiler/optimizing/stack_map_stream.h b/compiler/optimizing/stack_map_stream.h
index d634c70..02fb6cb 100644
--- a/compiler/optimizing/stack_map_stream.h
+++ b/compiler/optimizing/stack_map_stream.h
@@ -55,6 +55,8 @@
         in_inline_info_(false),
         current_inline_infos_(allocator->Adapter(kArenaAllocStackMapStream)),
         current_dex_registers_(allocator->Adapter(kArenaAllocStackMapStream)),
+        previous_dex_registers_(allocator->Adapter(kArenaAllocStackMapStream)),
+        dex_register_timestamp_(allocator->Adapter(kArenaAllocStackMapStream)),
         temp_dex_register_mask_(allocator, 32, true, kArenaAllocStackMapStream),
         temp_dex_register_map_(allocator->Adapter(kArenaAllocStackMapStream)) {
   }
@@ -113,8 +115,7 @@
     uint32_t method_info_index;
     uint32_t art_method_hi;
     uint32_t art_method_lo;
-    uint32_t dex_register_mask_index;
-    uint32_t dex_register_map_index;
+    uint32_t num_dex_registers;
   };
 
   // The fields must be uint32_t and mirror the InvokeInfo accessor in stack_map.h!
@@ -147,6 +148,7 @@
   BitmapTableBuilder dex_register_masks_;
   BitTableBuilder<uint32_t> dex_register_maps_;
   BitTableBuilder<DexRegisterEntry> dex_register_catalog_;
+  uint32_t num_dex_registers_ = 0;  // TODO: Make this const and get the value in constructor.
   ScopedArenaVector<uint8_t> out_;
 
   BitTableBuilder<uint32_t> method_infos_;
@@ -159,6 +161,8 @@
   StackMapEntry current_stack_map_;
   ScopedArenaVector<InlineInfoEntry> current_inline_infos_;
   ScopedArenaVector<DexRegisterLocation> current_dex_registers_;
+  ScopedArenaVector<DexRegisterLocation> previous_dex_registers_;
+  ScopedArenaVector<uint32_t> dex_register_timestamp_;  // Stack map index of last change.
   size_t expected_num_dex_registers_;
 
   // Temporary variables used in CreateDexRegisterMap.
diff --git a/compiler/optimizing/stack_map_test.cc b/compiler/optimizing/stack_map_test.cc
index 77aa3ef..9adc4c5 100644
--- a/compiler/optimizing/stack_map_test.cc
+++ b/compiler/optimizing/stack_map_test.cc
@@ -81,8 +81,8 @@
   ASSERT_TRUE(CheckStackMask(code_info, stack_map, sp_mask));
 
   ASSERT_TRUE(stack_map.HasDexRegisterMap());
-  DexRegisterMap dex_register_map =
-      code_info.GetDexRegisterMapOf(stack_map, number_of_dex_registers);
+  DexRegisterMap dex_register_map = code_info.GetDexRegisterMapOf(stack_map);
+  ASSERT_EQ(number_of_dex_registers, dex_register_map.size());
   ASSERT_TRUE(dex_register_map.IsDexRegisterLive(0));
   ASSERT_TRUE(dex_register_map.IsDexRegisterLive(1));
   ASSERT_EQ(2u, dex_register_map.GetNumberOfLiveDexRegisters());
@@ -170,8 +170,8 @@
     ASSERT_TRUE(CheckStackMask(code_info, stack_map, sp_mask1));
 
     ASSERT_TRUE(stack_map.HasDexRegisterMap());
-    DexRegisterMap dex_register_map =
-        code_info.GetDexRegisterMapOf(stack_map, number_of_dex_registers);
+    DexRegisterMap dex_register_map = code_info.GetDexRegisterMapOf(stack_map);
+    ASSERT_EQ(number_of_dex_registers, dex_register_map.size());
     ASSERT_TRUE(dex_register_map.IsDexRegisterLive(0));
     ASSERT_TRUE(dex_register_map.IsDexRegisterLive(1));
     ASSERT_EQ(2u, dex_register_map.GetNumberOfLiveDexRegisters());
@@ -210,8 +210,8 @@
     ASSERT_TRUE(CheckStackMask(code_info, stack_map, sp_mask2));
 
     ASSERT_TRUE(stack_map.HasDexRegisterMap());
-    DexRegisterMap dex_register_map =
-        code_info.GetDexRegisterMapOf(stack_map, number_of_dex_registers);
+    DexRegisterMap dex_register_map = code_info.GetDexRegisterMapOf(stack_map);
+    ASSERT_EQ(number_of_dex_registers, dex_register_map.size());
     ASSERT_TRUE(dex_register_map.IsDexRegisterLive(0));
     ASSERT_TRUE(dex_register_map.IsDexRegisterLive(1));
     ASSERT_EQ(2u, dex_register_map.GetNumberOfLiveDexRegisters());
@@ -243,8 +243,8 @@
     ASSERT_TRUE(CheckStackMask(code_info, stack_map, sp_mask3));
 
     ASSERT_TRUE(stack_map.HasDexRegisterMap());
-    DexRegisterMap dex_register_map =
-        code_info.GetDexRegisterMapOf(stack_map, number_of_dex_registers);
+    DexRegisterMap dex_register_map = code_info.GetDexRegisterMapOf(stack_map);
+    ASSERT_EQ(number_of_dex_registers, dex_register_map.size());
     ASSERT_TRUE(dex_register_map.IsDexRegisterLive(0));
     ASSERT_TRUE(dex_register_map.IsDexRegisterLive(1));
     ASSERT_EQ(2u, dex_register_map.GetNumberOfLiveDexRegisters());
@@ -276,8 +276,8 @@
     ASSERT_TRUE(CheckStackMask(code_info, stack_map, sp_mask4));
 
     ASSERT_TRUE(stack_map.HasDexRegisterMap());
-    DexRegisterMap dex_register_map =
-        code_info.GetDexRegisterMapOf(stack_map, number_of_dex_registers);
+    DexRegisterMap dex_register_map = code_info.GetDexRegisterMapOf(stack_map);
+    ASSERT_EQ(number_of_dex_registers, dex_register_map.size());
     ASSERT_TRUE(dex_register_map.IsDexRegisterLive(0));
     ASSERT_TRUE(dex_register_map.IsDexRegisterLive(1));
     ASSERT_EQ(2u, dex_register_map.GetNumberOfLiveDexRegisters());
@@ -342,7 +342,8 @@
     ASSERT_TRUE(CheckStackMask(code_info, stack_map, sp_mask1));
 
     ASSERT_TRUE(stack_map.HasDexRegisterMap());
-    DexRegisterMap map(code_info.GetDexRegisterMapOf(stack_map, number_of_dex_registers));
+    DexRegisterMap map(code_info.GetDexRegisterMapOf(stack_map));
+    ASSERT_EQ(number_of_dex_registers, map.size());
     ASSERT_TRUE(map.IsDexRegisterLive(0));
     ASSERT_TRUE(map.IsDexRegisterLive(1));
     ASSERT_EQ(2u, map.GetNumberOfLiveDexRegisters());
@@ -358,13 +359,6 @@
     ASSERT_EQ(Kind::kConstant, location1.GetKind());
     ASSERT_EQ(0, location0.GetValue());
     ASSERT_EQ(-2, location1.GetValue());
-
-    // Test that the inline info dex register map deduplicated to the same offset as the stack map
-    // one.
-    ASSERT_TRUE(stack_map.HasInlineInfo());
-    InlineInfo inline_info = code_info.GetInlineInfoAtDepth(stack_map, 0);
-    EXPECT_EQ(inline_info.GetDexRegisterMapIndex(),
-              stack_map.GetDexRegisterMapIndex());
   }
 }
 
@@ -400,8 +394,8 @@
   ASSERT_EQ(0x3u, code_info.GetRegisterMaskOf(stack_map));
 
   ASSERT_TRUE(stack_map.HasDexRegisterMap());
-  DexRegisterMap dex_register_map =
-      code_info.GetDexRegisterMapOf(stack_map, number_of_dex_registers);
+  DexRegisterMap dex_register_map = code_info.GetDexRegisterMapOf(stack_map);
+  ASSERT_EQ(number_of_dex_registers, dex_register_map.size());
   ASSERT_FALSE(dex_register_map.IsDexRegisterLive(0));
   ASSERT_TRUE(dex_register_map.IsDexRegisterLive(1));
   ASSERT_EQ(1u, dex_register_map.GetNumberOfLiveDexRegisters());
@@ -450,29 +444,28 @@
 
   // Verify first stack map.
   StackMap sm0 = ci.GetStackMapAt(0);
-  DexRegisterMap dex_registers0 = ci.GetDexRegisterMapOf(sm0, number_of_dex_registers);
+  DexRegisterMap dex_registers0 = ci.GetDexRegisterMapOf(sm0);
+  ASSERT_EQ(number_of_dex_registers, dex_registers0.size());
   ASSERT_EQ(0, dex_registers0.GetMachineRegister(0));
   ASSERT_EQ(-2, dex_registers0.GetConstant(1));
 
   // Verify second stack map.
   StackMap sm1 = ci.GetStackMapAt(1);
-  DexRegisterMap dex_registers1 = ci.GetDexRegisterMapOf(sm1, number_of_dex_registers);
+  DexRegisterMap dex_registers1 = ci.GetDexRegisterMapOf(sm1);
+  ASSERT_EQ(number_of_dex_registers, dex_registers1.size());
   ASSERT_EQ(0, dex_registers1.GetMachineRegister(0));
   ASSERT_EQ(-2, dex_registers1.GetConstant(1));
 
   // Verify third stack map.
   StackMap sm2 = ci.GetStackMapAt(2);
-  DexRegisterMap dex_registers2 = ci.GetDexRegisterMapOf(sm2, number_of_dex_registers);
+  DexRegisterMap dex_registers2 = ci.GetDexRegisterMapOf(sm2);
+  ASSERT_EQ(number_of_dex_registers, dex_registers2.size());
   ASSERT_EQ(2, dex_registers2.GetMachineRegister(0));
   ASSERT_EQ(-2, dex_registers2.GetConstant(1));
 
-  // Verify dex register map offsets.
-  ASSERT_EQ(sm0.GetDexRegisterMapIndex(),
-            sm1.GetDexRegisterMapIndex());
-  ASSERT_NE(sm0.GetDexRegisterMapIndex(),
-            sm2.GetDexRegisterMapIndex());
-  ASSERT_NE(sm1.GetDexRegisterMapIndex(),
-            sm2.GetDexRegisterMapIndex());
+  // Verify dex register mask offsets.
+  ASSERT_FALSE(sm1.HasDexRegisterMaskIndex());  // No delta.
+  ASSERT_TRUE(sm2.HasDexRegisterMaskIndex());  // Has delta.
 }
 
 TEST(StackMapTest, TestNoDexRegisterMap) {
@@ -602,7 +595,8 @@
     // Verify first stack map.
     StackMap sm0 = ci.GetStackMapAt(0);
 
-    DexRegisterMap dex_registers0 = ci.GetDexRegisterMapOf(sm0, 2);
+    DexRegisterMap dex_registers0 = ci.GetDexRegisterMapOf(sm0);
+    ASSERT_EQ(2u, dex_registers0.size());
     ASSERT_EQ(0, dex_registers0.GetStackOffsetInBytes(0));
     ASSERT_EQ(4, dex_registers0.GetConstant(1));
 
@@ -614,10 +608,12 @@
     ASSERT_EQ(3u, if0_1.GetDexPc());
     ASSERT_TRUE(if0_1.EncodesArtMethod());
 
-    DexRegisterMap dex_registers1 = ci.GetDexRegisterMapAtDepth(0, sm0, 1);
+    DexRegisterMap dex_registers1 = ci.GetDexRegisterMapAtDepth(0, sm0);
+    ASSERT_EQ(1u, dex_registers1.size());
     ASSERT_EQ(8, dex_registers1.GetStackOffsetInBytes(0));
 
-    DexRegisterMap dex_registers2 = ci.GetDexRegisterMapAtDepth(1, sm0, 3);
+    DexRegisterMap dex_registers2 = ci.GetDexRegisterMapAtDepth(1, sm0);
+    ASSERT_EQ(3u, dex_registers2.size());
     ASSERT_EQ(16, dex_registers2.GetStackOffsetInBytes(0));
     ASSERT_EQ(20, dex_registers2.GetConstant(1));
     ASSERT_EQ(15, dex_registers2.GetMachineRegister(2));
@@ -627,7 +623,8 @@
     // Verify second stack map.
     StackMap sm1 = ci.GetStackMapAt(1);
 
-    DexRegisterMap dex_registers0 = ci.GetDexRegisterMapOf(sm1, 2);
+    DexRegisterMap dex_registers0 = ci.GetDexRegisterMapOf(sm1);
+    ASSERT_EQ(2u, dex_registers0.size());
     ASSERT_EQ(56, dex_registers0.GetStackOffsetInBytes(0));
     ASSERT_EQ(0, dex_registers0.GetConstant(1));
 
@@ -642,22 +639,23 @@
     ASSERT_EQ(5u, if1_2.GetDexPc());
     ASSERT_TRUE(if1_2.EncodesArtMethod());
 
-    DexRegisterMap dex_registers1 = ci.GetDexRegisterMapAtDepth(0, sm1, 1);
+    DexRegisterMap dex_registers1 = ci.GetDexRegisterMapAtDepth(0, sm1);
+    ASSERT_EQ(1u, dex_registers1.size());
     ASSERT_EQ(12, dex_registers1.GetStackOffsetInBytes(0));
 
-    DexRegisterMap dex_registers2 = ci.GetDexRegisterMapAtDepth(1, sm1, 3);
+    DexRegisterMap dex_registers2 = ci.GetDexRegisterMapAtDepth(1, sm1);
+    ASSERT_EQ(3u, dex_registers2.size());
     ASSERT_EQ(80, dex_registers2.GetStackOffsetInBytes(0));
     ASSERT_EQ(10, dex_registers2.GetConstant(1));
     ASSERT_EQ(5, dex_registers2.GetMachineRegister(2));
-
-    ASSERT_FALSE(if1_2.HasDexRegisterMap());
   }
 
   {
     // Verify third stack map.
     StackMap sm2 = ci.GetStackMapAt(2);
 
-    DexRegisterMap dex_registers0 = ci.GetDexRegisterMapOf(sm2, 2);
+    DexRegisterMap dex_registers0 = ci.GetDexRegisterMapOf(sm2);
+    ASSERT_EQ(2u, dex_registers0.size());
     ASSERT_FALSE(dex_registers0.IsDexRegisterLive(0));
     ASSERT_EQ(4, dex_registers0.GetConstant(1));
     ASSERT_FALSE(sm2.HasInlineInfo());
@@ -667,7 +665,8 @@
     // Verify fourth stack map.
     StackMap sm3 = ci.GetStackMapAt(3);
 
-    DexRegisterMap dex_registers0 = ci.GetDexRegisterMapOf(sm3, 2);
+    DexRegisterMap dex_registers0 = ci.GetDexRegisterMapOf(sm3);
+    ASSERT_EQ(2u, dex_registers0.size());
     ASSERT_EQ(56, dex_registers0.GetStackOffsetInBytes(0));
     ASSERT_EQ(0, dex_registers0.GetConstant(1));
 
@@ -682,12 +681,12 @@
     ASSERT_EQ(10u, if2_2.GetDexPc());
     ASSERT_TRUE(if2_2.EncodesArtMethod());
 
-    ASSERT_FALSE(if2_0.HasDexRegisterMap());
-
-    DexRegisterMap dex_registers1 = ci.GetDexRegisterMapAtDepth(1, sm3, 1);
+    DexRegisterMap dex_registers1 = ci.GetDexRegisterMapAtDepth(1, sm3);
+    ASSERT_EQ(1u, dex_registers1.size());
     ASSERT_EQ(2, dex_registers1.GetMachineRegister(0));
 
-    DexRegisterMap dex_registers2 = ci.GetDexRegisterMapAtDepth(2, sm3, 2);
+    DexRegisterMap dex_registers2 = ci.GetDexRegisterMapAtDepth(2, sm3);
+    ASSERT_EQ(2u, dex_registers2.size());
     ASSERT_FALSE(dex_registers2.IsDexRegisterLive(0));
     ASSERT_EQ(3, dex_registers2.GetMachineRegister(1));
   }
diff --git a/compiler/utils/assembler_thumb_test_expected.cc.inc b/compiler/utils/assembler_thumb_test_expected.cc.inc
index 19c405e..e76e98a 100644
--- a/compiler/utils/assembler_thumb_test_expected.cc.inc
+++ b/compiler/utils/assembler_thumb_test_expected.cc.inc
@@ -153,7 +153,7 @@
   " 21c:	f8d9 8034 	ldr.w	r8, [r9, #52]	; 0x34\n",
   " 220:	4770      	bx	lr\n",
   " 222:	4660      	mov	r0, ip\n",
-  " 224:	f8d9 c2cc 	ldr.w	ip, [r9, #716]	; 0x2cc\n",
+  " 224:	f8d9 c2d0 	ldr.w	ip, [r9, #720]	; 0x2d0\n",
   " 228:	47e0      	blx	ip\n",
   nullptr
 };
diff --git a/dex2oat/linker/image_writer.cc b/dex2oat/linker/image_writer.cc
index 5f5930f..da69b83 100644
--- a/dex2oat/linker/image_writer.cc
+++ b/dex2oat/linker/image_writer.cc
@@ -1261,15 +1261,8 @@
   return nullptr;
 }
 
-
-ObjectArray<Object>* ImageWriter::CreateImageRoots(size_t oat_index) const {
-  Runtime* runtime = Runtime::Current();
-  ClassLinker* class_linker = runtime->GetClassLinker();
-  Thread* self = Thread::Current();
-  StackHandleScope<3> hs(self);
-  Handle<Class> object_array_class(hs.NewHandle(
-      class_linker->FindSystemClass(self, "[Ljava/lang/Object;")));
-
+ObjPtr<mirror::ObjectArray<mirror::Object>> ImageWriter::CollectDexCaches(Thread* self,
+                                                                          size_t oat_index) const {
   std::unordered_set<const DexFile*> image_dex_files;
   for (auto& pair : dex_file_oat_index_map_) {
     const DexFile* image_dex_file = pair.first;
@@ -1284,6 +1277,7 @@
   // ObjectArray, we lock the dex lock twice, first to get the number
   // of dex caches first and then lock it again to copy the dex
   // caches. We check that the number of dex caches does not change.
+  ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
   size_t dex_cache_count = 0;
   {
     ReaderMutexLock mu(self, *Locks::dex_lock_);
@@ -1300,8 +1294,8 @@
       }
     }
   }
-  Handle<ObjectArray<Object>> dex_caches(
-      hs.NewHandle(ObjectArray<Object>::Alloc(self, object_array_class.Get(), dex_cache_count)));
+  ObjPtr<ObjectArray<Object>> dex_caches = ObjectArray<Object>::Alloc(
+      self, GetClassRoot<ObjectArray<Object>>(class_linker), dex_cache_count);
   CHECK(dex_caches != nullptr) << "Failed to allocate a dex cache array.";
   {
     ReaderMutexLock mu(self, *Locks::dex_lock_);
@@ -1335,11 +1329,62 @@
       }
     }
   }
+  return dex_caches;
+}
+
+static ObjPtr<mirror::ObjectArray<mirror::Object>> LookupIntegerCache(Thread* self,
+                                                                      ClassLinker* class_linker)
+    REQUIRES_SHARED(Locks::mutator_lock_) {
+  ObjPtr<mirror::Class> integer_cache_class = class_linker->LookupClass(
+      self, "Ljava/lang/Integer$IntegerCache;", /* class_linker */ nullptr);
+  if (integer_cache_class == nullptr || !integer_cache_class->IsInitialized()) {
+    return nullptr;
+  }
+  ArtField* cache_field =
+      integer_cache_class->FindDeclaredStaticField("cache", "[Ljava/lang/Integer;");
+  CHECK(cache_field != nullptr);
+  ObjPtr<ObjectArray<mirror::Object>> integer_cache =
+      ObjPtr<ObjectArray<mirror::Object>>::DownCast(cache_field->GetObject(integer_cache_class));
+  CHECK(integer_cache != nullptr);
+  return integer_cache;
+}
+
+static ObjPtr<mirror::ObjectArray<mirror::Object>> CollectBootImageLiveObjects(
+    Thread* self,
+    ClassLinker* class_linker) REQUIRES_SHARED(Locks::mutator_lock_) {
+  // The objects used for the Integer.valueOf() intrinsic must remain live even if references
+  // to them are removed using reflection. Image roots are not accessible through reflection,
+  // so the array we construct here shall keep them alive.
+  StackHandleScope<1> hs(self);
+  Handle<ObjectArray<mirror::Object>> integer_cache =
+      hs.NewHandle(LookupIntegerCache(self, class_linker));
+  size_t live_objects_size =
+      (integer_cache != nullptr) ? (/* cache */ 1u + integer_cache->GetLength()) : 0u;
+  ObjPtr<mirror::ObjectArray<mirror::Object>> live_objects = ObjectArray<Object>::Alloc(
+      self, GetClassRoot<ObjectArray<Object>>(class_linker), live_objects_size);
+  int32_t index = 0;
+  if (integer_cache != nullptr) {
+    live_objects->Set(index++, integer_cache.Get());
+    for (int32_t i = 0, length = integer_cache->GetLength(); i != length; ++i) {
+      live_objects->Set(index++, integer_cache->Get(i));
+    }
+  }
+  CHECK_EQ(index, live_objects->GetLength());
+  return live_objects;
+}
+
+ObjectArray<Object>* ImageWriter::CreateImageRoots(size_t oat_index) const {
+  Runtime* runtime = Runtime::Current();
+  ClassLinker* class_linker = runtime->GetClassLinker();
+  Thread* self = Thread::Current();
+  StackHandleScope<2> hs(self);
+
+  Handle<ObjectArray<Object>> dex_caches(hs.NewHandle(CollectDexCaches(self, oat_index)));
 
   // build an Object[] of the roots needed to restore the runtime
   int32_t image_roots_size = ImageHeader::NumberOfImageRoots(compile_app_image_);
-  auto image_roots(hs.NewHandle(
-      ObjectArray<Object>::Alloc(self, object_array_class.Get(), image_roots_size)));
+  Handle<ObjectArray<Object>> image_roots(hs.NewHandle(ObjectArray<Object>::Alloc(
+      self, GetClassRoot<ObjectArray<Object>>(class_linker), image_roots_size)));
   image_roots->Set<false>(ImageHeader::kDexCaches, dex_caches.Get());
   image_roots->Set<false>(ImageHeader::kClassRoots, class_linker->GetClassRoots());
   image_roots->Set<false>(ImageHeader::kOomeWhenThrowingException,
@@ -1350,10 +1395,16 @@
                           runtime->GetPreAllocatedOutOfMemoryErrorWhenHandlingStackOverflow());
   image_roots->Set<false>(ImageHeader::kNoClassDefFoundError,
                           runtime->GetPreAllocatedNoClassDefFoundError());
-  // image_roots[ImageHeader::kClassLoader] will be set later for app image.
-  static_assert(ImageHeader::kClassLoader + 1u == ImageHeader::kImageRootsMax,
-                "Class loader should be the last image root.");
-  for (int32_t i = 0; i < ImageHeader::kImageRootsMax - 1; ++i) {
+  if (!compile_app_image_) {
+    ObjPtr<ObjectArray<Object>> boot_image_live_objects =
+        CollectBootImageLiveObjects(self, class_linker);
+    image_roots->Set<false>(ImageHeader::kBootImageLiveObjects, boot_image_live_objects);
+  }
+  for (int32_t i = 0, num = ImageHeader::NumberOfImageRoots(compile_app_image_); i != num; ++i) {
+    if (compile_app_image_ && i == ImageHeader::kAppImageClassLoader) {
+      // image_roots[ImageHeader::kAppImageClassLoader] will be set later for app image.
+      continue;
+    }
     CHECK(image_roots->Get(i) != nullptr);
   }
   return image_roots.Get();
@@ -1781,7 +1832,7 @@
     CHECK_EQ(class_loaders_.size(), 1u);
     CHECK_EQ(image_roots.size(), 1u);
     CHECK(*class_loaders_.begin() != nullptr);
-    image_roots[0]->Set<false>(ImageHeader::kClassLoader, *class_loaders_.begin());
+    image_roots[0]->Set<false>(ImageHeader::kAppImageClassLoader, *class_loaders_.begin());
   }
 
   // Verify that all objects have assigned image bin slots.
diff --git a/dex2oat/linker/image_writer.h b/dex2oat/linker/image_writer.h
index 960d698..c282a2a 100644
--- a/dex2oat/linker/image_writer.h
+++ b/dex2oat/linker/image_writer.h
@@ -450,6 +450,8 @@
       REQUIRES_SHARED(Locks::mutator_lock_);
   void CreateHeader(size_t oat_index)
       REQUIRES_SHARED(Locks::mutator_lock_);
+  ObjPtr<mirror::ObjectArray<mirror::Object>> CollectDexCaches(Thread* self, size_t oat_index) const
+      REQUIRES_SHARED(Locks::mutator_lock_);
   mirror::ObjectArray<mirror::Object>* CreateImageRoots(size_t oat_index) const
       REQUIRES_SHARED(Locks::mutator_lock_);
   void CalculateObjectBinSlots(mirror::Object* obj)
diff --git a/dex2oat/linker/oat_writer_test.cc b/dex2oat/linker/oat_writer_test.cc
index d0a6eb9..2bc286a 100644
--- a/dex2oat/linker/oat_writer_test.cc
+++ b/dex2oat/linker/oat_writer_test.cc
@@ -497,7 +497,7 @@
   EXPECT_EQ(76U, sizeof(OatHeader));
   EXPECT_EQ(4U, sizeof(OatMethodOffsets));
   EXPECT_EQ(24U, sizeof(OatQuickMethodHeader));
-  EXPECT_EQ(164 * static_cast<size_t>(GetInstructionSetPointerSize(kRuntimeISA)),
+  EXPECT_EQ(165 * static_cast<size_t>(GetInstructionSetPointerSize(kRuntimeISA)),
             sizeof(QuickEntryPoints));
 }
 
diff --git a/dexdump/dexdump.cc b/dexdump/dexdump.cc
index 85778b6..8261035 100644
--- a/dexdump/dexdump.cc
+++ b/dexdump/dexdump.cc
@@ -612,7 +612,7 @@
           pClassDef.class_data_off_, pClassDef.class_data_off_);
 
   // Fields and methods.
-  ClassAccessor accessor(*pDexFile, pClassDef);
+  ClassAccessor accessor(*pDexFile, idx);
   fprintf(gOutFile, "static_fields_size  : %d\n", accessor.NumStaticFields());
   fprintf(gOutFile, "instance_fields_size: %d\n", accessor.NumInstanceFields());
   fprintf(gOutFile, "direct_methods_size : %d\n", accessor.NumDirectMethods());
diff --git a/libartbase/base/bit_memory_region.h b/libartbase/base/bit_memory_region.h
index a3d3ee4..b4764fd 100644
--- a/libartbase/base/bit_memory_region.h
+++ b/libartbase/base/bit_memory_region.h
@@ -151,6 +151,20 @@
     StoreBits(bit_offset + bit, src.LoadBits(bit, num_bits), num_bits);
   }
 
+  // Count the number of set bits within the given bit range.
+  ALWAYS_INLINE size_t PopCount(size_t bit_offset, size_t bit_length) const {
+    DCHECK_LE(bit_offset, bit_size_);
+    DCHECK_LE(bit_length, bit_size_ - bit_offset);
+    size_t count = 0;
+    size_t bit = 0;
+    constexpr size_t kNumBits = BitSizeOf<uint32_t>();
+    for (; bit + kNumBits <= bit_length; bit += kNumBits) {
+      count += POPCOUNT(LoadBits(bit_offset + bit, kNumBits));
+    }
+    count += POPCOUNT(LoadBits(bit_offset + bit, bit_length - bit));
+    return count;
+  }
+
   ALWAYS_INLINE bool Equals(const BitMemoryRegion& other) const {
     return data_ == other.data_ &&
            bit_start_ == other.bit_start_ &&
diff --git a/libartbase/base/bit_table.h b/libartbase/base/bit_table.h
index bf3d3b0..0ae60b9 100644
--- a/libartbase/base/bit_table.h
+++ b/libartbase/base/bit_table.h
@@ -68,8 +68,10 @@
  public:
   class Accessor {
    public:
+    static constexpr uint32_t kCount = kNumColumns;
     static constexpr uint32_t kNoValue = std::numeric_limits<uint32_t>::max();
 
+    Accessor() {}
     Accessor(const BitTable* table, uint32_t row) : table_(table), row_(row) {}
 
     ALWAYS_INLINE uint32_t Row() const { return row_; }
@@ -86,14 +88,27 @@
       return this->table_ == other.table_ && this->row_ == other.row_;
     }
 
-    Accessor& operator++() {
-      row_++;
-      return *this;
-    }
+// Helper macro to create constructors and per-table utilities in derived class.
+#define BIT_TABLE_HEADER()                                                     \
+    using BitTable<kCount>::Accessor::Accessor; /* inherit the constructors */ \
+    template<int COLUMN, int UNUSED /*needed to compile*/> struct ColumnName;  \
+
+// Helper macro to create named column accessors in derived class.
+#define BIT_TABLE_COLUMN(COLUMN, NAME)                                         \
+    static constexpr uint32_t k##NAME = COLUMN;                                \
+    ALWAYS_INLINE uint32_t Get##NAME() const {                                 \
+      return table_->Get(row_, COLUMN);                                        \
+    }                                                                          \
+    ALWAYS_INLINE bool Has##NAME() const {                                     \
+      return table_->Get(row_, COLUMN) != kNoValue;                            \
+    }                                                                          \
+    template<int UNUSED> struct ColumnName<COLUMN, UNUSED> {                   \
+      static constexpr const char* Value = #NAME;                              \
+    };                                                                         \
 
    protected:
-    const BitTable* table_;
-    uint32_t row_;
+    const BitTable* table_ = nullptr;
+    uint32_t row_ = -1;
   };
 
   static constexpr uint32_t kValueBias = -1;
@@ -152,11 +167,17 @@
   uint16_t column_offset_[kNumColumns + 1] = {};
 };
 
-template<uint32_t kNumColumns>
-constexpr uint32_t BitTable<kNumColumns>::Accessor::kNoValue;
+// Template meta-programming helper.
+template<typename Accessor, size_t... Columns>
+static const char** GetBitTableColumnNamesImpl(std::index_sequence<Columns...>) {
+  static const char* names[] = { Accessor::template ColumnName<Columns, 0>::Value... };
+  return names;
+}
 
-template<uint32_t kNumColumns>
-constexpr uint32_t BitTable<kNumColumns>::kValueBias;
+template<typename Accessor>
+static const char** GetBitTableColumnNames() {
+  return GetBitTableColumnNamesImpl<Accessor>(std::make_index_sequence<Accessor::kCount>());
+}
 
 // Helper class for encoding BitTable. It can optionally de-duplicate the inputs.
 // Type 'T' must be POD type consisting of uint32_t fields (one for each column).
@@ -209,18 +230,6 @@
     return index;
   }
 
-  // Check if the table already contains given values starting at the given index.
-  bool RangeEquals(uint32_t index, T* values, size_t count = 1) {
-    DCHECK_LE(index, size());
-    DCHECK_LE(count, size() - index);
-    for (uint32_t i = 0; i < count; i++) {
-      if (memcmp(&values[i], &rows_[index + i], sizeof(T)) != 0) {
-        return false;
-      }
-    }
-    return true;
-  }
-
   ALWAYS_INLINE uint32_t Get(uint32_t row, uint32_t column) const {
     DCHECK_LT(row, size());
     DCHECK_LT(column, kNumColumns);
@@ -290,9 +299,6 @@
   ScopedArenaUnorderedMultimap<uint32_t, uint32_t> dedup_;  // Hash -> row index.
 };
 
-template<typename T>
-constexpr size_t BitTableBuilder<T>::kNumColumns;
-
 // Helper class for encoding single-column BitTable of bitmaps (allows more than 32 bits).
 class BitmapTableBuilder {
  public:
diff --git a/libdexfile/dex/class_accessor-inl.h b/libdexfile/dex/class_accessor-inl.h
index 3bb9e93..a335f08 100644
--- a/libdexfile/dex/class_accessor-inl.h
+++ b/libdexfile/dex/class_accessor-inl.h
@@ -26,12 +26,15 @@
 namespace art {
 
 inline ClassAccessor::ClassAccessor(const ClassIteratorData& data)
-    : ClassAccessor(data.dex_file_, data.dex_file_.GetClassDef(data.class_def_idx_)) {}
+    : ClassAccessor(data.dex_file_, data.class_def_idx_) {}
 
 inline ClassAccessor::ClassAccessor(const DexFile& dex_file, const DexFile::ClassDef& class_def)
+    : ClassAccessor(dex_file, dex_file.GetIndexForClassDef(class_def)) {}
+
+inline ClassAccessor::ClassAccessor(const DexFile& dex_file, uint32_t class_def_index)
     : dex_file_(dex_file),
-      descriptor_index_(class_def.class_idx_),
-      ptr_pos_(dex_file.GetClassData(class_def)),
+      class_def_index_(class_def_index),
+      ptr_pos_(dex_file.GetClassData(dex_file.GetClassDef(class_def_index))),
       num_static_fields_(ptr_pos_ != nullptr ? DecodeUnsignedLeb128(&ptr_pos_) : 0u),
       num_instance_fields_(ptr_pos_ != nullptr ? DecodeUnsignedLeb128(&ptr_pos_) : 0u),
       num_direct_methods_(ptr_pos_ != nullptr ? DecodeUnsignedLeb128(&ptr_pos_) : 0u),
@@ -108,7 +111,7 @@
 }
 
 inline const char* ClassAccessor::GetDescriptor() const {
-  return dex_file_.StringByTypeIdx(descriptor_index_);
+  return dex_file_.StringByTypeIdx(GetClassIdx());
 }
 
 inline const DexFile::CodeItem* ClassAccessor::Method::GetCodeItem() const {
@@ -175,6 +178,10 @@
   DexFile::UnHideAccessFlags(const_cast<uint8_t*>(ptr_pos_), GetAccessFlags(), /*is_method*/ true);
 }
 
+inline dex::TypeIndex ClassAccessor::GetClassIdx() const {
+  return dex_file_.GetClassDef(class_def_index_).class_idx_;
+}
+
 }  // namespace art
 
 #endif  // ART_LIBDEXFILE_DEX_CLASS_ACCESSOR_INL_H_
diff --git a/libdexfile/dex/class_accessor.h b/libdexfile/dex/class_accessor.h
index 4f0fd32..34c7e07 100644
--- a/libdexfile/dex/class_accessor.h
+++ b/libdexfile/dex/class_accessor.h
@@ -248,6 +248,8 @@
 
   ClassAccessor(const DexFile& dex_file, const DexFile::ClassDef& class_def);
 
+  ClassAccessor(const DexFile& dex_file, uint32_t class_def_index);
+
   // Return the code item for a method.
   const DexFile::CodeItem* GetCodeItem(const Method& method) const;
 
@@ -315,9 +317,7 @@
 
   const char* GetDescriptor() const;
 
-  dex::TypeIndex GetClassIdx() const {
-    return descriptor_index_;
-  }
+  dex::TypeIndex GetClassIdx() const;
 
   const DexFile& GetDexFile() const {
     return dex_file_;
@@ -327,6 +327,10 @@
     return ptr_pos_ != nullptr;
   }
 
+  uint32_t GetClassDefIndex() const {
+    return class_def_index_;
+  }
+
  protected:
   // Template visitor to reduce copy paste for visiting elements.
   // No thread safety analysis since the visitor may require capabilities.
@@ -341,7 +345,7 @@
   IterationRange<DataIterator<Method>> GetMethodsInternal(size_t count) const;
 
   const DexFile& dex_file_;
-  const dex::TypeIndex descriptor_index_ = {};
+  const uint32_t class_def_index_;
   const uint8_t* ptr_pos_ = nullptr;  // Pointer into stream of class_data_item.
   const uint32_t num_static_fields_ = 0u;
   const uint32_t num_instance_fields_ = 0u;
diff --git a/libdexfile/dex/class_accessor_test.cc b/libdexfile/dex/class_accessor_test.cc
index d0533c1..1f30ae5 100644
--- a/libdexfile/dex/class_accessor_test.cc
+++ b/libdexfile/dex/class_accessor_test.cc
@@ -30,8 +30,9 @@
     uint32_t class_def_idx = 0u;
     ASSERT_GT(dex_file->NumClassDefs(), 0u);
     for (ClassAccessor accessor : dex_file->GetClasses()) {
-      const DexFile::ClassDef& class_def = dex_file->GetClassDef(class_def_idx);
+      const DexFile::ClassDef& class_def = dex_file->GetClassDef(accessor.GetClassDefIndex());
       EXPECT_EQ(accessor.GetDescriptor(), dex_file->StringByTypeIdx(class_def.class_idx_));
+      EXPECT_EQ(class_def_idx, accessor.GetClassDefIndex());
       ++class_def_idx;
       // Check iterators against visitors.
       auto methods = accessor.GetMethods();
diff --git a/libdexfile/dex/dex_file.cc b/libdexfile/dex/dex_file.cc
index f570158..f1f8960 100644
--- a/libdexfile/dex/dex_file.cc
+++ b/libdexfile/dex/dex_file.cc
@@ -605,6 +605,15 @@
   return PrettyDescriptor(GetTypeDescriptor(type_id));
 }
 
+dex::ProtoIndex DexFile::GetProtoIndexForCallSite(uint32_t call_site_idx) const {
+  const DexFile::CallSiteIdItem& csi = GetCallSiteId(call_site_idx);
+  CallSiteArrayValueIterator it(*this, csi);
+  it.Next();
+  it.Next();
+  DCHECK_EQ(EncodedArrayValueIterator::ValueType::kMethodType, it.GetValueType());
+  return dex::ProtoIndex(it.GetJavaValue().i);
+}
+
 // Checks that visibility is as expected. Includes special behavior for M and
 // before to allow runtime and build visibility when expecting runtime.
 std::ostream& operator<<(std::ostream& os, const DexFile& dex_file) {
diff --git a/libdexfile/dex/dex_file.h b/libdexfile/dex/dex_file.h
index ed21980..a8cd187 100644
--- a/libdexfile/dex/dex_file.h
+++ b/libdexfile/dex/dex_file.h
@@ -737,6 +737,8 @@
     return DataBegin() + call_site_id.data_off_;
   }
 
+  dex::ProtoIndex GetProtoIndexForCallSite(uint32_t call_site_idx) const;
+
   static const TryItem* GetTryItems(const DexInstructionIterator& code_item_end, uint32_t offset);
 
   // Get the base of the encoded data for the given DexCode.
diff --git a/libdexfile/dex/invoke_type.h b/libdexfile/dex/invoke_type.h
index 9b3af67..1740c07 100644
--- a/libdexfile/dex/invoke_type.h
+++ b/libdexfile/dex/invoke_type.h
@@ -28,7 +28,8 @@
   kSuper,        // <<super>>
   kInterface,    // <<interface>>
   kPolymorphic,  // <<polymorphic>>
-  kMaxInvokeType = kPolymorphic
+  kCustom,       // <<custom>>
+  kMaxInvokeType = kCustom
 };
 
 std::ostream& operator<<(std::ostream& os, const InvokeType& rhs);
diff --git a/oatdump/oatdump.cc b/oatdump/oatdump.cc
index 7b72e18..453e9da 100644
--- a/oatdump/oatdump.cc
+++ b/oatdump/oatdump.cc
@@ -274,7 +274,7 @@
   void WalkOatClass(const OatFile::OatClass& oat_class,
                     const DexFile& dex_file,
                     uint32_t class_def_index) {
-    ClassAccessor accessor(dex_file, dex_file.GetClassDef(class_def_index));
+    ClassAccessor accessor(dex_file, class_def_index);
     // Note: even if this is an interface or a native class, we still have to walk it, as there
     //       might be a static initializer.
     uint32_t class_method_idx = 0;
@@ -757,7 +757,7 @@
       kByteKindInlineInfoMethodIndexIdx,
       kByteKindInlineInfoDexPc,
       kByteKindInlineInfoArtMethod,
-      kByteKindInlineInfoDexRegisterMap,
+      kByteKindInlineInfoNumDexRegisters,
       kByteKindInlineInfoIsLast,
       kByteKindCount,
       // Special ranges for std::accumulate convenience.
@@ -859,8 +859,8 @@
                inline_info_bits,
                "inline info");
           Dump(os,
-               "InlineInfoDexRegisterMap      ",
-               bits[kByteKindInlineInfoDexRegisterMap],
+               "InlineInfoNumDexRegisters     ",
+               bits[kByteKindInlineInfoNumDexRegisters],
                inline_info_bits,
                "inline info");
           Dump(os,
@@ -905,15 +905,13 @@
         continue;
       }
       offsets_.insert(reinterpret_cast<uintptr_t>(&dex_file->GetHeader()));
-      uint32_t class_def_index = 0u;
       for (ClassAccessor accessor : dex_file->GetClasses()) {
-        const OatFile::OatClass oat_class = oat_dex_file->GetOatClass(class_def_index);
+        const OatFile::OatClass oat_class = oat_dex_file->GetOatClass(accessor.GetClassDefIndex());
         for (uint32_t class_method_index = 0;
             class_method_index < accessor.NumMethods();
             ++class_method_index) {
           AddOffsets(oat_class.GetOatMethod(class_method_index));
         }
-        ++class_def_index;
       }
     }
 
@@ -1429,7 +1427,7 @@
         DCHECK(code_item_accessor.HasCodeItem());
         ScopedIndentation indent1(vios);
         MethodInfo method_info = oat_method.GetOatQuickMethodHeader()->GetOptimizedMethodInfo();
-        DumpCodeInfo(vios, code_info, oat_method, code_item_accessor, method_info);
+        DumpCodeInfo(vios, code_info, oat_method, method_info);
       }
     } else if (IsMethodGeneratedByDexToDexCompiler(oat_method, code_item_accessor)) {
       // We don't encode the size in the table, so just emit that we have quickened
@@ -1445,11 +1443,9 @@
   void DumpCodeInfo(VariableIndentationOutputStream* vios,
                     const CodeInfo& code_info,
                     const OatFile::OatMethod& oat_method,
-                    const CodeItemDataAccessor& code_item_accessor,
                     const MethodInfo& method_info) {
     code_info.Dump(vios,
                    oat_method.GetCodeOffset(),
-                   code_item_accessor.RegistersSize(),
                    options_.dump_code_info_stack_maps_,
                    instruction_set_,
                    method_info);
@@ -1752,7 +1748,7 @@
           if (num_inline_infos > 0u) {
             stats_.AddBits(
                 Stats::kByteKindInlineInfoMethodIndexIdx,
-                inline_infos.NumColumnBits(InlineInfo::kMethodIndexIdx) * num_inline_infos);
+                inline_infos.NumColumnBits(InlineInfo::kMethodInfoIndex) * num_inline_infos);
             stats_.AddBits(
                 Stats::kByteKindInlineInfoDexPc,
                 inline_infos.NumColumnBits(InlineInfo::kDexPc) * num_inline_infos);
@@ -1761,8 +1757,8 @@
                 inline_infos.NumColumnBits(InlineInfo::kArtMethodHi) * num_inline_infos +
                 inline_infos.NumColumnBits(InlineInfo::kArtMethodLo) * num_inline_infos);
             stats_.AddBits(
-                Stats::kByteKindInlineInfoDexRegisterMap,
-                inline_infos.NumColumnBits(InlineInfo::kDexRegisterMapIndex) * num_inline_infos);
+                Stats::kByteKindInlineInfoNumDexRegisters,
+                inline_infos.NumColumnBits(InlineInfo::kNumberOfDexRegisters) * num_inline_infos);
             stats_.AddBits(Stats::kByteKindInlineInfoIsLast, num_inline_infos);
           }
         }
@@ -1779,7 +1775,6 @@
                          helper.GetCodeInfo(),
                          method_info,
                          oat_method.GetCodeOffset(),
-                         code_item_accessor.RegistersSize(),
                          instruction_set_);
           do {
             helper.Next();
diff --git a/patchoat/patchoat.cc b/patchoat/patchoat.cc
index 108c753..0beca1f 100644
--- a/patchoat/patchoat.cc
+++ b/patchoat/patchoat.cc
@@ -363,6 +363,10 @@
     uint32_t offset_delta = 0;
     if (DecodeUnsignedLeb128Checked(&rel_ptr, rel_end, &offset_delta)) {
       offset += offset_delta;
+      if (static_cast<int64_t>(offset) + static_cast<int64_t>(sizeof(uint32_t)) > image_size) {
+        *error_msg = StringPrintf("Relocation out of bounds in %s", relocated_filename.c_str());
+        return false;
+      }
       uint32_t *image_value = reinterpret_cast<uint32_t*>(image_start + offset);
       *image_value -= expected_diff;
     } else {
diff --git a/patchoat/patchoat_test.cc b/patchoat/patchoat_test.cc
index 69728ae..08bf31c 100644
--- a/patchoat/patchoat_test.cc
+++ b/patchoat/patchoat_test.cc
@@ -445,103 +445,173 @@
 #endif
 }
 
-TEST_F(PatchoatTest, RelFileVerification) {
-  // This test checks that a boot image relocated using patchoat can be unrelocated using the .rel
-  // file created by patchoat.
+// These tests check that a boot image relocated using patchoat can be unrelocated
+// using the .rel file created by patchoat.
+//
+// The tests don't work when heap poisoning is enabled because some of the
+// references are negated. b/72117833 is tracking the effort to have patchoat
+// and its tests support heap poisoning.
+class PatchoatVerificationTest : public PatchoatTest {
+ protected:
+  void CreateRelocatedBootImage() {
+    // Compile boot image into a random directory using dex2oat
+    ScratchFile dex2oat_orig_scratch;
+    dex2oat_orig_scratch.Unlink();
+    dex2oat_orig_dir_ = dex2oat_orig_scratch.GetFilename();
+    ASSERT_EQ(0, mkdir(dex2oat_orig_dir_.c_str(), 0700));
+    const uint32_t orig_base_addr = 0x60000000;
+    std::vector<std::string> dex2oat_extra_args;
+    std::string error_msg;
+    if (!CompileBootImageToDir(dex2oat_orig_dir_, dex2oat_extra_args, orig_base_addr, &error_msg)) {
+      FAIL() << "CompileBootImage1 failed: " << error_msg;
+    }
 
-  // This test doesn't work when heap poisoning is enabled because some of the
-  // references are negated. b/72117833 is tracking the effort to have patchoat
-  // and its tests support heap poisoning.
+    // Generate image relocation file for the original boot image
+    std::string dex2oat_orig_with_arch_dir =
+        dex2oat_orig_dir_ + "/" + GetInstructionSetString(kRuntimeISA);
+    // The arch-including symlink is needed by patchoat
+    ASSERT_EQ(0, symlink(dex2oat_orig_dir_.c_str(), dex2oat_orig_with_arch_dir.c_str()));
+    base_addr_delta_ = 0x100000;
+    if (!GenerateBootImageRelFile(
+        dex2oat_orig_dir_ + "/boot.art",
+        dex2oat_orig_dir_,
+        base_addr_delta_,
+        &error_msg)) {
+      FAIL() << "RelocateBootImage failed: " << error_msg;
+    }
+
+    // Relocate the original boot image using patchoat
+    ScratchFile relocated_scratch;
+    relocated_scratch.Unlink();
+    relocated_dir_ = relocated_scratch.GetFilename();
+    ASSERT_EQ(0, mkdir(relocated_dir_.c_str(), 0700));
+    // Use a different relocation delta from the one used when generating .rel files above. This is
+    // to make sure .rel files are not specific to a particular relocation delta.
+    base_addr_delta_ -= 0x10000;
+    if (!RelocateBootImage(
+        dex2oat_orig_dir_ + "/boot.art",
+        relocated_dir_,
+        base_addr_delta_,
+        &error_msg)) {
+      FAIL() << "RelocateBootImage failed: " << error_msg;
+    }
+
+    // Assert that patchoat created the same set of .art and .art.rel files
+    std::vector<std::string> rel_basenames;
+    std::vector<std::string> relocated_image_basenames;
+    if (!ListDirFilesEndingWith(dex2oat_orig_dir_, ".rel", &rel_basenames, &error_msg)) {
+      FAIL() << "Failed to list *.art.rel files in " << dex2oat_orig_dir_ << ": " << error_msg;
+    }
+    if (!ListDirFilesEndingWith(relocated_dir_, ".art", &relocated_image_basenames, &error_msg)) {
+      FAIL() << "Failed to list *.art files in " << relocated_dir_ << ": " << error_msg;
+    }
+    std::sort(rel_basenames.begin(), rel_basenames.end());
+    std::sort(relocated_image_basenames.begin(), relocated_image_basenames.end());
+
+    // .art and .art.rel file names output by patchoat look like
+    // tmp@art-data-<random>-<random>@boot*.art, encoding the name of the directory in their name.
+    // To compare these with each other, we retain only the part of the file name after the last @,
+    // and we also drop the extension.
+    std::vector<std::string> rel_shortened_basenames(rel_basenames.size());
+    std::vector<std::string> relocated_image_shortened_basenames(relocated_image_basenames.size());
+    for (size_t i = 0; i < rel_basenames.size(); i++) {
+      rel_shortened_basenames[i] = rel_basenames[i].substr(rel_basenames[i].find_last_of("@") + 1);
+      rel_shortened_basenames[i] =
+          rel_shortened_basenames[i].substr(0, rel_shortened_basenames[i].find("."));
+    }
+    for (size_t i = 0; i < relocated_image_basenames.size(); i++) {
+      relocated_image_shortened_basenames[i] =
+          relocated_image_basenames[i].substr(relocated_image_basenames[i].find_last_of("@") + 1);
+      relocated_image_shortened_basenames[i] =
+          relocated_image_shortened_basenames[i].substr(
+              0, relocated_image_shortened_basenames[i].find("."));
+    }
+    ASSERT_EQ(rel_shortened_basenames, relocated_image_shortened_basenames);
+  }
+
+  virtual void TearDown() {
+    if (!dex2oat_orig_dir_.empty()) {
+      ClearDirectory(dex2oat_orig_dir_.c_str(), /*recursive*/ true);
+      rmdir(dex2oat_orig_dir_.c_str());
+    }
+    if (!relocated_dir_.empty()) {
+      ClearDirectory(relocated_dir_.c_str(), /*recursive*/ true);
+      rmdir(relocated_dir_.c_str());
+    }
+    PatchoatTest::TearDown();
+  }
+
+  std::string dex2oat_orig_dir_;
+  std::string relocated_dir_;
+  off_t base_addr_delta_;
+};
+
+// Assert that verification works with the .rel files.
+TEST_F(PatchoatVerificationTest, Sucessful) {
   TEST_DISABLED_FOR_HEAP_POISONING();
+  CreateRelocatedBootImage();
 
-  // Compile boot image into a random directory using dex2oat
-  ScratchFile dex2oat_orig_scratch;
-  dex2oat_orig_scratch.Unlink();
-  std::string dex2oat_orig_dir = dex2oat_orig_scratch.GetFilename();
-  ASSERT_EQ(0, mkdir(dex2oat_orig_dir.c_str(), 0700));
-  const uint32_t orig_base_addr = 0x60000000;
-  std::vector<std::string> dex2oat_extra_args;
   std::string error_msg;
-  if (!CompileBootImageToDir(dex2oat_orig_dir, dex2oat_extra_args, orig_base_addr, &error_msg)) {
-    FAIL() << "CompileBootImage1 failed: " << error_msg;
-  }
-
-  // Generate image relocation file for the original boot image
-  std::string dex2oat_orig_with_arch_dir =
-      dex2oat_orig_dir + "/" + GetInstructionSetString(kRuntimeISA);
-  // The arch-including symlink is needed by patchoat
-  ASSERT_EQ(0, symlink(dex2oat_orig_dir.c_str(), dex2oat_orig_with_arch_dir.c_str()));
-  off_t base_addr_delta = 0x100000;
-  if (!GenerateBootImageRelFile(
-      dex2oat_orig_dir + "/boot.art",
-      dex2oat_orig_dir,
-      base_addr_delta,
-      &error_msg)) {
-    FAIL() << "RelocateBootImage failed: " << error_msg;
-  }
-
-  // Relocate the original boot image using patchoat
-  ScratchFile relocated_scratch;
-  relocated_scratch.Unlink();
-  std::string relocated_dir = relocated_scratch.GetFilename();
-  ASSERT_EQ(0, mkdir(relocated_dir.c_str(), 0700));
-  // Use a different relocation delta from the one used when generating .rel files above. This is
-  // to make sure .rel files are not specific to a particular relocation delta.
-  base_addr_delta -= 0x10000;
-  if (!RelocateBootImage(
-      dex2oat_orig_dir + "/boot.art",
-      relocated_dir,
-      base_addr_delta,
-      &error_msg)) {
-    FAIL() << "RelocateBootImage failed: " << error_msg;
-  }
-
-  // Assert that patchoat created the same set of .art and .art.rel files
-  std::vector<std::string> rel_basenames;
-  std::vector<std::string> relocated_image_basenames;
-  if (!ListDirFilesEndingWith(dex2oat_orig_dir, ".rel", &rel_basenames, &error_msg)) {
-    FAIL() << "Failed to list *.art.rel files in " << dex2oat_orig_dir << ": " << error_msg;
-  }
-  if (!ListDirFilesEndingWith(relocated_dir, ".art", &relocated_image_basenames, &error_msg)) {
-    FAIL() << "Failed to list *.art files in " << relocated_dir << ": " << error_msg;
-  }
-  std::sort(rel_basenames.begin(), rel_basenames.end());
-  std::sort(relocated_image_basenames.begin(), relocated_image_basenames.end());
-
-  // .art and .art.rel file names output by patchoat look like
-  // tmp@art-data-<random>-<random>@boot*.art, encoding the name of the directory in their name.
-  // To compare these with each other, we retain only the part of the file name after the last @,
-  // and we also drop the extension.
-  std::vector<std::string> rel_shortened_basenames(rel_basenames.size());
-  std::vector<std::string> relocated_image_shortened_basenames(relocated_image_basenames.size());
-  for (size_t i = 0; i < rel_basenames.size(); i++) {
-    rel_shortened_basenames[i] = rel_basenames[i].substr(rel_basenames[i].find_last_of("@") + 1);
-    rel_shortened_basenames[i] =
-        rel_shortened_basenames[i].substr(0, rel_shortened_basenames[i].find("."));
-  }
-  for (size_t i = 0; i < relocated_image_basenames.size(); i++) {
-    relocated_image_shortened_basenames[i] =
-        relocated_image_basenames[i].substr(relocated_image_basenames[i].find_last_of("@") + 1);
-    relocated_image_shortened_basenames[i] =
-        relocated_image_shortened_basenames[i].substr(
-            0, relocated_image_shortened_basenames[i].find("."));
-  }
-  ASSERT_EQ(rel_shortened_basenames, relocated_image_shortened_basenames);
-
-  // Assert that verification works with the .rel files.
   if (!VerifyBootImage(
-      dex2oat_orig_dir + "/boot.art",
-      relocated_dir,
-      base_addr_delta,
+      dex2oat_orig_dir_ + "/boot.art",
+      relocated_dir_,
+      base_addr_delta_,
       &error_msg)) {
     FAIL() << "VerifyBootImage failed: " << error_msg;
   }
+}
 
-  ClearDirectory(dex2oat_orig_dir.c_str(), /*recursive*/ true);
-  ClearDirectory(relocated_dir.c_str(), /*recursive*/ true);
+// Corrupt the image file and check that the verification fails gracefully.
+TEST_F(PatchoatVerificationTest, CorruptedImage) {
+  TEST_DISABLED_FOR_HEAP_POISONING();
+  CreateRelocatedBootImage();
 
-  rmdir(dex2oat_orig_dir.c_str());
-  rmdir(relocated_dir.c_str());
+  std::string error_msg;
+  std::string relocated_image_filename;
+  if (!GetDalvikCacheFilename((dex2oat_orig_dir_ + "/boot.art").c_str(),
+                               relocated_dir_.c_str(),
+                               &relocated_image_filename,
+                               &error_msg)) {
+    FAIL() << "Failed to find relocated image file name: " << error_msg;
+  }
+  ASSERT_EQ(truncate(relocated_image_filename.c_str(), sizeof(ImageHeader)), 0)
+    << relocated_image_filename;
+
+  if (VerifyBootImage(
+      dex2oat_orig_dir_ + "/boot.art",
+      relocated_dir_,
+      base_addr_delta_,
+      &error_msg)) {
+    FAIL() << "VerifyBootImage should have failed since the image was intentionally corrupted";
+  }
+}
+
+// Corrupt the relocation file and check that the verification fails gracefully.
+TEST_F(PatchoatVerificationTest, CorruptedRelFile) {
+  TEST_DISABLED_FOR_HEAP_POISONING();
+  CreateRelocatedBootImage();
+
+  std::string error_msg;
+  std::string art_filename = dex2oat_orig_dir_ + "/boot.art";
+  std::string rel_filename = dex2oat_orig_dir_ + "/boot.art.rel";
+  std::unique_ptr<File> art_file(OS::OpenFileForReading(art_filename.c_str()));
+  std::unique_ptr<File> rel_file(OS::OpenFileReadWrite(rel_filename.c_str()));
+  rel_file->ClearContent();
+  uint8_t buffer[64] = {};
+  ASSERT_TRUE(rel_file->WriteFully(&buffer, SHA256_DIGEST_LENGTH));
+  // Encode single relocation which is just past the end of the image file.
+  size_t leb_size = EncodeUnsignedLeb128(buffer, art_file->GetLength()) - buffer;
+  ASSERT_TRUE(rel_file->WriteFully(&buffer, leb_size));
+  ASSERT_EQ(rel_file->FlushClose(), 0);
+  ASSERT_EQ(art_file->Close(), 0);
+
+  if (VerifyBootImage(
+      dex2oat_orig_dir_ + "/boot.art",
+      relocated_dir_,
+      base_addr_delta_,
+      &error_msg)) {
+    FAIL() << "VerifyBootImage should have failed since the rel file was intentionally corrupted";
+  }
 }
 
 }  // namespace art
diff --git a/profman/profman.cc b/profman/profman.cc
index 096e5dc..5fbce66 100644
--- a/profman/profman.cc
+++ b/profman/profman.cc
@@ -930,7 +930,9 @@
       dex_resolved_classes.first->AddClass(class_ref.TypeIndex());
       std::vector<ProfileMethodInfo> methods;
       if (method_str == kClassAllMethods) {
-        ClassAccessor accessor(*dex_file, *dex_file->FindClassDef(class_ref.TypeIndex()));
+        ClassAccessor accessor(
+            *dex_file,
+            dex_file->GetIndexForClassDef(*dex_file->FindClassDef(class_ref.TypeIndex())));
         for (const ClassAccessor::Method& method : accessor.GetMethods()) {
           if (method.GetCodeItemOffset() != 0) {
             // Add all of the methods that have code to the profile.
diff --git a/runtime/arch/arm/quick_entrypoints_arm.S b/runtime/arch/arm/quick_entrypoints_arm.S
index 311e838..ccff9f6 100644
--- a/runtime/arch/arm/quick_entrypoints_arm.S
+++ b/runtime/arch/arm/quick_entrypoints_arm.S
@@ -2686,84 +2686,31 @@
 .extern artInvokePolymorphic
 ENTRY art_quick_invoke_polymorphic
     SETUP_SAVE_REFS_AND_ARGS_FRAME r2
-    mov     r2, rSELF              @ pass Thread::Current
-    mov     r3, sp                 @ pass SP
-    mov     r0, #0                 @ initialize 64-bit JValue as zero.
-    str     r0, [sp, #-4]!
-    .cfi_adjust_cfa_offset 4
-    str     r0, [sp, #-4]!
-    .cfi_adjust_cfa_offset 4
-    mov     r0, sp                 @ pass JValue for return result as first argument.
-    bl      artInvokePolymorphic   @ artInvokePolymorphic(JValue, receiver, Thread*, SP)
-    sub     r0, 'A'                @ return value is descriptor of handle's return type.
-    cmp     r0, 'Z' - 'A'          @ check if value is in bounds of handler table
-    bgt     .Lcleanup_and_return   @ and clean-up if not.
-    adr     r1, .Lhandler_table
-    tbb     [r0, r1]               @ branch to handler for return value based on return type.
-
-.Lstart_of_handlers:
-.Lstore_boolean_result:
-    ldrb    r0, [sp]               @ Copy boolean value to return value of this function.
-    b       .Lcleanup_and_return
-.Lstore_char_result:
-    ldrh    r0, [sp]               @ Copy char value to return value of this function.
-    b       .Lcleanup_and_return
-.Lstore_float_result:
-    vldr    s0, [sp]               @ Copy float value from JValue result to the context restored by
-    vstr    s0, [sp, #16]          @ RESTORE_SAVE_REFS_AND_ARGS_FRAME.
-    b       .Lcleanup_and_return
-.Lstore_double_result:
-    vldr    d0, [sp]               @ Copy double value from JValue result to the context restored by
-    vstr    d0, [sp, #16]          @ RESTORE_SAVE_REFS_AND_ARGS_FRAME.
-    b       .Lcleanup_and_return
-.Lstore_long_result:
-    ldr     r1, [sp, #4]           @ Copy the upper bits from JValue result to the context restored by
-    str     r1, [sp, #80]          @ RESTORE_SAVE_REFS_AND_ARGS_FRAME.
-    // Fall-through for lower bits.
-.Lstore_int_result:
-    ldr     r0, [sp]               @ Copy int value to return value of this function.
-    // Fall-through to clean up and return.
-.Lcleanup_and_return:
-    add     sp, #8
-    .cfi_adjust_cfa_offset -8
+    mov     r0, r1                 @ r0 := receiver
+    mov     r1, rSELF              @ r1 := Thread::Current
+    mov     r2, sp                 @ r2 := SP
+    bl      artInvokePolymorphic   @ artInvokePolymorphic(receiver, Thread*, SP)
+    str     r1, [sp, 72]           @ r0:r1 := Result. Copy r1 to context.
     RESTORE_SAVE_REFS_AND_ARGS_FRAME
     REFRESH_MARKING_REGISTER
+    vmov    d0, r0, r1             @ Put result r0:r1 into floating point return register.
     RETURN_OR_DELIVER_PENDING_EXCEPTION_REG r2
-
-.macro HANDLER_TABLE_OFFSET handler_label
-    .byte (\handler_label - .Lstart_of_handlers) / 2
-.endm
-
-.Lhandler_table:
-    HANDLER_TABLE_OFFSET(.Lcleanup_and_return)    // A
-    HANDLER_TABLE_OFFSET(.Lstore_int_result)      // B (byte)
-    HANDLER_TABLE_OFFSET(.Lstore_char_result)     // C (char)
-    HANDLER_TABLE_OFFSET(.Lstore_double_result)   // D (double)
-    HANDLER_TABLE_OFFSET(.Lcleanup_and_return)    // E
-    HANDLER_TABLE_OFFSET(.Lstore_float_result)    // F (float)
-    HANDLER_TABLE_OFFSET(.Lcleanup_and_return)    // G
-    HANDLER_TABLE_OFFSET(.Lcleanup_and_return)    // H
-    HANDLER_TABLE_OFFSET(.Lstore_int_result)      // I (int)
-    HANDLER_TABLE_OFFSET(.Lstore_long_result)     // J (long)
-    HANDLER_TABLE_OFFSET(.Lcleanup_and_return)    // K
-    HANDLER_TABLE_OFFSET(.Lstore_int_result)      // L (object)
-    HANDLER_TABLE_OFFSET(.Lcleanup_and_return)    // M
-    HANDLER_TABLE_OFFSET(.Lcleanup_and_return)    // N
-    HANDLER_TABLE_OFFSET(.Lcleanup_and_return)    // O
-    HANDLER_TABLE_OFFSET(.Lcleanup_and_return)    // P
-    HANDLER_TABLE_OFFSET(.Lcleanup_and_return)    // Q
-    HANDLER_TABLE_OFFSET(.Lcleanup_and_return)    // R
-    HANDLER_TABLE_OFFSET(.Lstore_int_result)      // S (short)
-    HANDLER_TABLE_OFFSET(.Lcleanup_and_return)    // T
-    HANDLER_TABLE_OFFSET(.Lcleanup_and_return)    // U
-    HANDLER_TABLE_OFFSET(.Lcleanup_and_return)    // V (void)
-    HANDLER_TABLE_OFFSET(.Lcleanup_and_return)    // W
-    HANDLER_TABLE_OFFSET(.Lcleanup_and_return)    // X
-    HANDLER_TABLE_OFFSET(.Lcleanup_and_return)    // Y
-    HANDLER_TABLE_OFFSET(.Lstore_boolean_result)  // Z (boolean)
-.purgem HANDLER_TABLE_OFFSET
 END art_quick_invoke_polymorphic
 
+.extern artInvokeCustom
+ENTRY art_quick_invoke_custom
+    SETUP_SAVE_REFS_AND_ARGS_FRAME r1
+                                   @ r0 := call_site_idx
+    mov     r1, rSELF              @ r1 := Thread::Current
+    mov     r2, sp                 @ r2 := SP
+    bl      artInvokeCustom        @ artInvokeCustom(call_site_idx, Thread*, SP)
+    str     r1, [sp, #72]          @ Save r1 to context (r0:r1 = result)
+    RESTORE_SAVE_REFS_AND_ARGS_FRAME
+    REFRESH_MARKING_REGISTER
+    vmov    d0, r0, r1             @ Put result r0:r1 into floating point return register.
+    RETURN_OR_DELIVER_PENDING_EXCEPTION_REG r2
+END art_quick_invoke_custom
+
 // Wrap ExecuteSwitchImpl in assembly method which specifies DEX PC for unwinding.
 //  Argument 0: r0: The context pointer for ExecuteSwitchImpl.
 //  Argument 1: r1: Pointer to the templated ExecuteSwitchImpl to call.
diff --git a/runtime/arch/arm64/quick_entrypoints_arm64.S b/runtime/arch/arm64/quick_entrypoints_arm64.S
index 14d0cc7..80d5fce 100644
--- a/runtime/arch/arm64/quick_entrypoints_arm64.S
+++ b/runtime/arch/arm64/quick_entrypoints_arm64.S
@@ -2844,84 +2844,30 @@
 
 .extern artInvokePolymorphic
 ENTRY art_quick_invoke_polymorphic
-    SETUP_SAVE_REFS_AND_ARGS_FRAME                // Save callee saves in case allocation triggers GC.
-    mov     x2, xSELF
-    mov     x3, sp
-    INCREASE_FRAME 16                             // Reserve space for JValue result.
-    str     xzr, [sp, #0]                         // Initialize result to zero.
-    mov     x0, sp                                // Set r0 to point to result.
-    bl      artInvokePolymorphic                  // artInvokePolymorphic(result, receiver, thread, save_area)
-    uxtb    w0, w0                                // Result is the return type descriptor as a char.
-    sub     w0, w0, 'A'                           // Convert to zero based index.
-    cmp     w0, 'Z' - 'A'
-    bhi     .Lcleanup_and_return                  // Clean-up if out-of-bounds.
-    adrp    x1, .Lhandler_table                   // Compute address of handler table.
-    add     x1, x1, :lo12:.Lhandler_table
-    ldrb    w0, [x1, w0, uxtw]                    // Lookup handler offset in handler table.
-    adr     x1, .Lstart_of_handlers
-    add     x0, x1, w0, sxtb #2                   // Convert relative offset to absolute address.
-    br      x0                                    // Branch to handler.
-
-.Lstart_of_handlers:
-.Lstore_boolean_result:
-    ldrb    w0, [sp]
-    b       .Lcleanup_and_return
-.Lstore_char_result:
-    ldrh    w0, [sp]
-    b       .Lcleanup_and_return
-.Lstore_float_result:
-    ldr     s0, [sp]
-    str     s0, [sp, #32]
-    b       .Lcleanup_and_return
-.Lstore_double_result:
-    ldr     d0, [sp]
-    str     d0, [sp, #32]
-    b       .Lcleanup_and_return
-.Lstore_long_result:
-    ldr     x0, [sp]
-    // Fall-through
-.Lcleanup_and_return:
-    DECREASE_FRAME 16
+    SETUP_SAVE_REFS_AND_ARGS_FRAME      // Save callee saves in case allocation triggers GC.
+    mov     x0, x1                      // x0 := receiver
+    mov     x1, xSELF                   // x1 := Thread::Current()
+    mov     x2, sp                      // x2 := SP
+    bl      artInvokePolymorphic        // artInvokePolymorphic(receiver, thread, save_area)
     RESTORE_SAVE_REFS_AND_ARGS_FRAME
     REFRESH_MARKING_REGISTER
-    RETURN_OR_DELIVER_PENDING_EXCEPTION_X1
-
-    .section    .rodata                           // Place handler table in read-only section away from text.
-    .align  2
-.macro HANDLER_TABLE_OFFSET handler_label
-    .byte (\handler_label - .Lstart_of_handlers) / 4
-.endm
-.Lhandler_table:
-    HANDLER_TABLE_OFFSET(.Lcleanup_and_return)    // A
-    HANDLER_TABLE_OFFSET(.Lstore_long_result)     // B (byte)
-    HANDLER_TABLE_OFFSET(.Lstore_char_result)     // C (char)
-    HANDLER_TABLE_OFFSET(.Lstore_double_result)   // D (double)
-    HANDLER_TABLE_OFFSET(.Lcleanup_and_return)    // E
-    HANDLER_TABLE_OFFSET(.Lstore_float_result)    // F (float)
-    HANDLER_TABLE_OFFSET(.Lcleanup_and_return)    // G
-    HANDLER_TABLE_OFFSET(.Lcleanup_and_return)    // H
-    HANDLER_TABLE_OFFSET(.Lstore_long_result)     // I (int)
-    HANDLER_TABLE_OFFSET(.Lstore_long_result)     // J (long)
-    HANDLER_TABLE_OFFSET(.Lcleanup_and_return)    // K
-    HANDLER_TABLE_OFFSET(.Lstore_long_result)     // L (object - references are compressed and only 32-bits)
-    HANDLER_TABLE_OFFSET(.Lcleanup_and_return)    // M
-    HANDLER_TABLE_OFFSET(.Lcleanup_and_return)    // N
-    HANDLER_TABLE_OFFSET(.Lcleanup_and_return)    // O
-    HANDLER_TABLE_OFFSET(.Lcleanup_and_return)    // P
-    HANDLER_TABLE_OFFSET(.Lcleanup_and_return)    // Q
-    HANDLER_TABLE_OFFSET(.Lcleanup_and_return)    // R
-    HANDLER_TABLE_OFFSET(.Lstore_long_result)     // S (short)
-    HANDLER_TABLE_OFFSET(.Lcleanup_and_return)    // T
-    HANDLER_TABLE_OFFSET(.Lcleanup_and_return)    // U
-    HANDLER_TABLE_OFFSET(.Lcleanup_and_return)    // V (void)
-    HANDLER_TABLE_OFFSET(.Lcleanup_and_return)    // W
-    HANDLER_TABLE_OFFSET(.Lcleanup_and_return)    // X
-    HANDLER_TABLE_OFFSET(.Lcleanup_and_return)    // Y
-    HANDLER_TABLE_OFFSET(.Lstore_boolean_result)  // Z (boolean)
-    .text
-
+    fmov    d0, x0                      // Result is in x0. Copy to floating return register.
+    RETURN_OR_DELIVER_PENDING_EXCEPTION
 END  art_quick_invoke_polymorphic
 
+.extern artInvokeCustom
+ENTRY art_quick_invoke_custom
+    SETUP_SAVE_REFS_AND_ARGS_FRAME    // Save callee saves in case allocation triggers GC.
+                                      // x0 := call_site_idx
+    mov     x1, xSELF                 // x1 := Thread::Current()
+    mov     x2, sp                    // x2 := SP
+    bl      artInvokeCustom           // artInvokeCustom(call_site_idx, thread, save_area)
+    RESTORE_SAVE_REFS_AND_ARGS_FRAME
+    REFRESH_MARKING_REGISTER
+    fmov    d0, x0                    // Copy result to double result register.
+    RETURN_OR_DELIVER_PENDING_EXCEPTION
+END  art_quick_invoke_custom
+
 // Wrap ExecuteSwitchImpl in assembly method which specifies DEX PC for unwinding.
 //  Argument 0: x0: The context pointer for ExecuteSwitchImpl.
 //  Argument 1: x1: Pointer to the templated ExecuteSwitchImpl to call.
diff --git a/runtime/arch/mips/entrypoints_init_mips.cc b/runtime/arch/mips/entrypoints_init_mips.cc
index 5d6e410..2b69c17 100644
--- a/runtime/arch/mips/entrypoints_init_mips.cc
+++ b/runtime/arch/mips/entrypoints_init_mips.cc
@@ -410,6 +410,9 @@
   static_assert(!IsDirectEntrypoint(kQuickInvokeVirtualTrampolineWithAccessCheck),
                 "Non-direct C stub marked direct.");
   qpoints->pInvokePolymorphic = art_quick_invoke_polymorphic;
+  static_assert(!IsDirectEntrypoint(kQuickInvokePolymorphic), "Non-direct C stub marked direct.");
+  qpoints->pInvokeCustom = art_quick_invoke_custom;
+  static_assert(!IsDirectEntrypoint(kQuickInvokeCustom), "Non-direct C stub marked direct.");
 
   // Thread
   qpoints->pTestSuspend = art_quick_test_suspend;
diff --git a/runtime/arch/mips/quick_entrypoints_mips.S b/runtime/arch/mips/quick_entrypoints_mips.S
index c367ea6..508a201 100644
--- a/runtime/arch/mips/quick_entrypoints_mips.S
+++ b/runtime/arch/mips/quick_entrypoints_mips.S
@@ -3246,59 +3246,48 @@
     BRB_FIELD_EXIT_BREAK
 END art_quick_read_barrier_mark_introspection
 
+    /*
+     * Polymorphic method invocation.
+     * On entry:
+     *   a0 = unused
+     *   a1 = receiver
+     */
 .extern artInvokePolymorphic
 ENTRY art_quick_invoke_polymorphic
     SETUP_SAVE_REFS_AND_ARGS_FRAME
-    move  $a2, rSELF                          # Make $a2 an alias for the current Thread.
-    addiu $a3, $sp, ARG_SLOT_SIZE             # Make $a3 a pointer to the saved frame context.
-    sw    $zero, 20($sp)                      # Initialize JValue result.
-    sw    $zero, 16($sp)
-    la    $t9, artInvokePolymorphic
-    jalr  $t9                                 # artInvokePolymorphic(result, receiver, Thread*, context)
-    addiu $a0, $sp, 16                        # Make $a0 a pointer to the JValue result
-.macro MATCH_RETURN_TYPE c, handler
-    li    $t0, \c
-    beq   $v0, $t0, \handler
-.endm
-    MATCH_RETURN_TYPE 'V', .Lcleanup_and_return
-    MATCH_RETURN_TYPE 'L', .Lstore_int_result
-    MATCH_RETURN_TYPE 'I', .Lstore_int_result
-    MATCH_RETURN_TYPE 'J', .Lstore_long_result
-    MATCH_RETURN_TYPE 'B', .Lstore_int_result
-    MATCH_RETURN_TYPE 'C', .Lstore_char_result
-    MATCH_RETURN_TYPE 'D', .Lstore_double_result
-    MATCH_RETURN_TYPE 'F', .Lstore_float_result
-    MATCH_RETURN_TYPE 'S', .Lstore_int_result
-    MATCH_RETURN_TYPE 'Z', .Lstore_boolean_result
-.purgem MATCH_RETURN_TYPE
-    nop
-    b .Lcleanup_and_return
-    nop
-.Lstore_boolean_result:
-    b .Lcleanup_and_return
-    lbu   $v0, 16($sp)                        # Move byte from JValue result to return value register.
-.Lstore_char_result:
-    b .Lcleanup_and_return
-    lhu   $v0, 16($sp)                        # Move char from JValue result to return value register.
-.Lstore_double_result:
-.Lstore_float_result:
-    CHECK_ALIGNMENT $sp, $t0
-    ldc1  $f0, 16($sp)                        # Move double/float from JValue result to return value register.
-    b .Lcleanup_and_return
-    nop
-.Lstore_long_result:
-    lw    $v1, 20($sp)                        # Move upper bits from JValue result to return value register.
-    // Fall-through for lower bits.
-.Lstore_int_result:
-    lw    $v0, 16($sp)                        # Move lower bits from JValue result to return value register.
-    // Fall-through to clean up and return.
-.Lcleanup_and_return:
-    lw    $t7, THREAD_EXCEPTION_OFFSET(rSELF) # Load Thread::Current()->exception_
+    move    $a0, $a1                            # Make $a0 the receiver.
+    move    $a1, rSELF                          # Make $a1 an alias for the current Thread.
+    la      $t9, artInvokePolymorphic           # Invoke artInvokePolymorphic
+    jalr    $t9                                 # with args (receiver, Thread*, context).
+    addiu   $a2, $sp, ARG_SLOT_SIZE             # Make $a2 a pointer to the saved frame context.
+    lw      $t7, THREAD_EXCEPTION_OFFSET(rSELF) # load Thread::Current()->exception_
     RESTORE_SAVE_REFS_AND_ARGS_FRAME
-    bnez  $t7, 1f                             # Success if no exception is pending.
-    nop
-    jalr  $zero, $ra
+    bnez    $t7, 1f
+    # don't care if $v0 and/or $v1 are modified, when exception branch taken
+    MTD     $v0, $v1, $f0, $f1                  # move float value to return value
+    jalr    $zero, $ra
     nop
 1:
     DELIVER_PENDING_EXCEPTION
 END art_quick_invoke_polymorphic
+
+    /*
+     * InvokeCustom invocation.
+     * On entry:
+     *   a0 = call_site_idx
+     */
+.extern artInvokeCustom
+ENTRY art_quick_invoke_custom
+    SETUP_SAVE_REFS_AND_ARGS_FRAME
+    move    $a1, rSELF                          # Make $a1 an alias for the current Thread.
+    la      $t9, artInvokeCustom                # Invoke artInvokeCustom
+    jalr    $t9                                 # with args (call_site_idx, Thread*, context).
+    addiu   $a2, $sp, ARG_SLOT_SIZE             # Make $a2 a pointer to the saved frame context.
+    lw      $t7, THREAD_EXCEPTION_OFFSET(rSELF) # load Thread::Current()->exception_
+    RESTORE_SAVE_REFS_AND_ARGS_FRAME
+    bnez    $t7, 1f
+    # don't care if $v0 and/or $v1 are modified, when exception branch taken
+    MTD     $v0, $v1, $f0, $f1                  # move float value to return value
+    jalr    $zero, $ra
+    nop
+END art_quick_invoke_custom
diff --git a/runtime/arch/mips64/quick_entrypoints_mips64.S b/runtime/arch/mips64/quick_entrypoints_mips64.S
index 1f4f174..258acdd 100644
--- a/runtime/arch/mips64/quick_entrypoints_mips64.S
+++ b/runtime/arch/mips64/quick_entrypoints_mips64.S
@@ -3046,61 +3046,49 @@
     BRB_FIELD_EXIT_BREAK
 END art_quick_read_barrier_mark_introspection
 
+    /*
+     * Polymorphic method invocation.
+     * On entry:
+     *   a0 = unused
+     *   a1 = receiver
+     */
 .extern artInvokePolymorphic
 ENTRY art_quick_invoke_polymorphic
     SETUP_SAVE_REFS_AND_ARGS_FRAME
-    move   $a2, rSELF                          # Make $a2 an alias for the current Thread.
-    move   $a3, $sp                            # Make $a3 a pointer to the saved frame context.
-    daddiu $sp, $sp, -8                        # Reserve space for JValue result.
-    .cfi_adjust_cfa_offset 8
-    sd     $zero, 0($sp)                       # Initialize JValue result.
-    jal    artInvokePolymorphic                # artInvokePolymorphic(result, receiver, Thread*, context)
-    move   $a0, $sp                            # Make $a0 a pointer to the JValue result
-.macro MATCH_RETURN_TYPE c, handler
-    li     $t0, \c
-    beq    $v0, $t0, \handler
-.endm
-    MATCH_RETURN_TYPE 'V', .Lcleanup_and_return
-    MATCH_RETURN_TYPE 'L', .Lstore_ref_result
-    MATCH_RETURN_TYPE 'I', .Lstore_long_result
-    MATCH_RETURN_TYPE 'J', .Lstore_long_result
-    MATCH_RETURN_TYPE 'B', .Lstore_long_result
-    MATCH_RETURN_TYPE 'C', .Lstore_char_result
-    MATCH_RETURN_TYPE 'D', .Lstore_double_result
-    MATCH_RETURN_TYPE 'F', .Lstore_float_result
-    MATCH_RETURN_TYPE 'S', .Lstore_long_result
-    MATCH_RETURN_TYPE 'Z', .Lstore_boolean_result
-.purgem MATCH_RETURN_TYPE
-    nop
-    b .Lcleanup_and_return
-    nop
-.Lstore_boolean_result:
-    b      .Lcleanup_and_return
-    lbu    $v0, 0($sp)                         # Move byte from JValue result to return value register.
-.Lstore_char_result:
-    b      .Lcleanup_and_return
-    lhu    $v0, 0($sp)                         # Move char from JValue result to return value register.
-.Lstore_double_result:
-.Lstore_float_result:
-    b      .Lcleanup_and_return
-    l.d    $f0, 0($sp)                         # Move double/float from JValue result to return value register.
-.Lstore_ref_result:
-    b      .Lcleanup_and_return
-    lwu    $v0, 0($sp)                         # Move zero extended lower 32-bits to return value register.
-.Lstore_long_result:
-    ld     $v0, 0($sp)                         # Move long from JValue result to return value register.
-    // Fall-through to clean up and return.
-.Lcleanup_and_return:
-    daddiu $sp, $sp, 8                         # Remove space for JValue result.
-    .cfi_adjust_cfa_offset -8
-    ld     $t0, THREAD_EXCEPTION_OFFSET(rSELF) # Load Thread::Current()->exception_
-    RESTORE_SAVE_REFS_AND_ARGS_FRAME
-    bnez   $t0, 1f                             # Success if no exception is pending.
-    nop
-    jalr   $zero, $ra
-    nop
+    move    $a0, $a1               # Make $a0 the receiver
+    move    $a1, rSELF             # Make $a1 an alias for the current Thread.
+    jal     artInvokePolymorphic   # artInvokePolymorphic(receiver, Thread*, context)
+    move    $a2, $sp               # Make $a3 a pointer to the saved frame context.
+    ld      $t0, THREAD_EXCEPTION_OFFSET(rSELF) # load Thread::Current()->exception_
+    daddiu  $sp, $sp, REFS_AND_ARGS_MINUS_REFS_SIZE  # skip a0-a7 and f12-f19
+    RESTORE_SAVE_REFS_ONLY_FRAME
+    bne     $t0, $zero, 1f
+    dmtc1   $v0, $f0               # place return value to FP return value
+    jalr    $zero, $ra
+    dmtc1   $v1, $f1               # place return value to FP return value
 1:
     DELIVER_PENDING_EXCEPTION
 END art_quick_invoke_polymorphic
 
+    /*
+     * InvokeCustom invocation.
+     * On entry:
+     *   a0 = call_site_idx
+     */
+.extern artInvokeCustom
+ENTRY art_quick_invoke_custom
+    SETUP_SAVE_REFS_AND_ARGS_FRAME
+    move    $a1, rSELF             # Make $a1 an alias for the current Thread.
+    jal     artInvokeCustom        # Call artInvokeCustom(call_site_idx, Thread*, context).
+    move    $a2, $sp               # Make $a1 a pointer to the saved frame context.
+    ld      $t0, THREAD_EXCEPTION_OFFSET(rSELF) # load Thread::Current()->exception_
+    daddiu  $sp, $sp, REFS_AND_ARGS_MINUS_REFS_SIZE  # skip a0-a7 and f12-f19
+    RESTORE_SAVE_REFS_ONLY_FRAME
+    bne     $t0, $zero, 1f
+    dmtc1   $v0, $f0               # place return value to FP return value
+    jalr    $zero, $ra
+    dmtc1   $v1, $f1               # place return value to FP return value
+1:
+    DELIVER_PENDING_EXCEPTION
+END art_quick_invoke_polymorphic
   .set pop
diff --git a/runtime/arch/x86/quick_entrypoints_x86.S b/runtime/arch/x86/quick_entrypoints_x86.S
index b89d45f..e1b3df8 100644
--- a/runtime/arch/x86/quick_entrypoints_x86.S
+++ b/runtime/arch/x86/quick_entrypoints_x86.S
@@ -2434,99 +2434,49 @@
 END_FUNCTION art_quick_osr_stub
 
 DEFINE_FUNCTION art_quick_invoke_polymorphic
-    SETUP_SAVE_REFS_AND_ARGS_FRAME  ebx, ebx       // Save frame.
-    mov %esp, %edx                                 // Remember SP.
-    subl LITERAL(16), %esp                         // Make space for JValue result.
-    CFI_ADJUST_CFA_OFFSET(16)
-    movl LITERAL(0), (%esp)                        // Initialize result to zero.
-    movl LITERAL(0), 4(%esp)
-    mov %esp, %eax                                 // Store pointer to JValue result in eax.
-    PUSH edx                                       // pass SP
-    pushl %fs:THREAD_SELF_OFFSET                   // pass Thread::Current()
+                                                   // On entry: EAX := unused, ECX := receiver
+    SETUP_SAVE_REFS_AND_ARGS_FRAME ebx, ebx        // Save frame.
+    mov %esp, %edx                                 // Remember SP
+    sub LITERAL(4), %esp                           // Alignment padding
     CFI_ADJUST_CFA_OFFSET(4)
-    PUSH ecx                                       // pass receiver (method handle)
-    PUSH eax                                       // pass JResult
-    call SYMBOL(artInvokePolymorphic)              // artInvokePolymorphic(result, receiver, Thread*, SP)
-    subl LITERAL('A'), %eax                        // Eliminate out of bounds options
-    cmpb LITERAL('Z' - 'A'), %al
-    ja .Lcleanup_and_return
-    movzbl %al, %eax
-    call .Lput_eip_in_ecx
-.Lbranch_start:
-    movl %ecx, %edx
-    add $(.Lhandler_table - .Lbranch_start), %edx  // Make EDX point to handler_table.
-    leal (%edx, %eax, 2), %eax                     // Calculate address of entry in table.
-    movzwl (%eax), %eax                            // Lookup relative branch in table.
-    addl %ecx, %eax                                // Add EIP relative offset.
-    jmp *%eax                                      // Branch to handler.
-
-    // Handlers for different return types.
-.Lstore_boolean_result:
-    movzbl 16(%esp), %eax                          // Copy boolean result to the accumulator.
-    jmp .Lcleanup_and_return
-.Lstore_char_result:
-    movzwl 16(%esp), %eax                          // Copy char result to the accumulator.
-    jmp .Lcleanup_and_return
-.Lstore_float_result:
-    movd 16(%esp), %xmm0                           // Copy float result to the context restored by
-    movd %xmm0, 36(%esp)                           // RESTORE_SAVE_REFS_ONLY_FRAME.
-    jmp .Lcleanup_and_return
-.Lstore_double_result:
-    movsd 16(%esp), %xmm0                          // Copy double result to the context restored by
-    movsd %xmm0, 36(%esp)                          // RESTORE_SAVE_REFS_ONLY_FRAME.
-    jmp .Lcleanup_and_return
-.Lstore_long_result:
-    movl 20(%esp), %edx                            // Copy upper-word of result to the context restored by
-    movl %edx, 72(%esp)                            // RESTORE_SAVE_REFS_ONLY_FRAME.
-    // Fall-through for lower bits.
-.Lstore_int_result:
-    movl 16(%esp), %eax                            // Copy int result to the accumulator.
-    // Fall-through to clean up and return.
-.Lcleanup_and_return:
-    addl LITERAL(32), %esp                         // Pop arguments and stack allocated JValue result.
-    CFI_ADJUST_CFA_OFFSET(-32)
+    push %edx                                      // Push SP
+    CFI_ADJUST_CFA_OFFSET(4)
+    pushl %fs:THREAD_SELF_OFFSET                   // Push Thread::Current()
+    CFI_ADJUST_CFA_OFFSET(4)
+    push %ecx                                      // Push receiver (method handle)
+    CFI_ADJUST_CFA_OFFSET(4)
+    call SYMBOL(artInvokePolymorphic)              // invoke with (receiver, thread, SP)
+    addl LITERAL(16), %esp                         // Pop arguments.
+    CFI_ADJUST_CFA_OFFSET(-16)
+    mov %eax, 4(%esp)                              // Result is in EAX:EDX. Copy to saved FP state.
+    mov %edx, 8(%esp)
+    mov %edx, 40(%esp)                             // Copy EDX to saved context
     RESTORE_SAVE_REFS_AND_ARGS_FRAME
     RETURN_OR_DELIVER_PENDING_EXCEPTION
-
-.Lput_eip_in_ecx:                                  // Internal function that puts address of
-    movl 0(%esp), %ecx                             // next instruction into ECX when CALL
-    ret
-
-    // Handler table to handlers for given type.
-.Lhandler_table:
-MACRO1(HANDLER_TABLE_ENTRY, handler_label)
-    // NB some tools require 16-bits for relocations. Shouldn't need adjusting.
-    .word RAW_VAR(handler_label) - .Lbranch_start
-END_MACRO
-    HANDLER_TABLE_ENTRY(.Lcleanup_and_return)      // A
-    HANDLER_TABLE_ENTRY(.Lstore_int_result)        // B (byte)
-    HANDLER_TABLE_ENTRY(.Lstore_char_result)       // C (char)
-    HANDLER_TABLE_ENTRY(.Lstore_double_result)     // D (double)
-    HANDLER_TABLE_ENTRY(.Lcleanup_and_return)      // E
-    HANDLER_TABLE_ENTRY(.Lstore_float_result)      // F (float)
-    HANDLER_TABLE_ENTRY(.Lcleanup_and_return)      // G
-    HANDLER_TABLE_ENTRY(.Lcleanup_and_return)      // H
-    HANDLER_TABLE_ENTRY(.Lstore_int_result)        // I (int)
-    HANDLER_TABLE_ENTRY(.Lstore_long_result)       // J (long)
-    HANDLER_TABLE_ENTRY(.Lcleanup_and_return)      // K
-    HANDLER_TABLE_ENTRY(.Lstore_int_result)        // L (object)
-    HANDLER_TABLE_ENTRY(.Lcleanup_and_return)      // M
-    HANDLER_TABLE_ENTRY(.Lcleanup_and_return)      // N
-    HANDLER_TABLE_ENTRY(.Lcleanup_and_return)      // O
-    HANDLER_TABLE_ENTRY(.Lcleanup_and_return)      // P
-    HANDLER_TABLE_ENTRY(.Lcleanup_and_return)      // Q
-    HANDLER_TABLE_ENTRY(.Lcleanup_and_return)      // R
-    HANDLER_TABLE_ENTRY(.Lstore_int_result)        // S (short)
-    HANDLER_TABLE_ENTRY(.Lcleanup_and_return)      // T
-    HANDLER_TABLE_ENTRY(.Lcleanup_and_return)      // U
-    HANDLER_TABLE_ENTRY(.Lcleanup_and_return)      // V (void)
-    HANDLER_TABLE_ENTRY(.Lcleanup_and_return)      // W
-    HANDLER_TABLE_ENTRY(.Lcleanup_and_return)      // X
-    HANDLER_TABLE_ENTRY(.Lcleanup_and_return)      // Y
-    HANDLER_TABLE_ENTRY(.Lstore_boolean_result)    // Z (boolean)
-
 END_FUNCTION art_quick_invoke_polymorphic
 
+DEFINE_FUNCTION art_quick_invoke_custom
+    SETUP_SAVE_REFS_AND_ARGS_FRAME ebx, ebx        // Save frame.
+                                                   // EAX := call_site_index
+    mov %esp, %ecx                                 // Remember SP.
+    subl LITERAL(4), %esp                          // Alignment padding.
+    CFI_ADJUST_CFA_OFFSET(4)
+    push %ecx                                      // pass SP
+    CFI_ADJUST_CFA_OFFSET(4)
+    pushl %fs:THREAD_SELF_OFFSET                   // pass Thread::Current()
+    CFI_ADJUST_CFA_OFFSET(4)
+    push %eax                                      // pass call_site_index
+    CFI_ADJUST_CFA_OFFSET(4)
+    call SYMBOL(artInvokeCustom)                   // artInvokeCustom(call_site_index, Thread*, SP)
+    addl LITERAL(16), %esp                         // Pop arguments.
+    CFI_ADJUST_CFA_OFFSET(-16)
+    mov %eax, 4(%esp)                              // Result is in EAX:EDX. Copy to saved FP state.
+    mov %edx, 8(%esp)
+    mov %edx, 40(%esp)                             // Copy EDX to saved context
+    RESTORE_SAVE_REFS_AND_ARGS_FRAME
+    RETURN_OR_DELIVER_PENDING_EXCEPTION
+END_FUNCTION art_quick_invoke_custom
+
 // Wrap ExecuteSwitchImpl in assembly method which specifies DEX PC for unwinding.
 //  Argument 0: ESP+4: The context pointer for ExecuteSwitchImpl.
 //  Argument 1: ESP+8: Pointer to the templated ExecuteSwitchImpl to call.
diff --git a/runtime/arch/x86_64/quick_entrypoints_x86_64.S b/runtime/arch/x86_64/quick_entrypoints_x86_64.S
index c179033..9980966 100644
--- a/runtime/arch/x86_64/quick_entrypoints_x86_64.S
+++ b/runtime/arch/x86_64/quick_entrypoints_x86_64.S
@@ -2418,81 +2418,29 @@
 END_FUNCTION art_quick_osr_stub
 
 DEFINE_FUNCTION art_quick_invoke_polymorphic
+                                                   // On entry: RDI := unused, RSI := receiver
     SETUP_SAVE_REFS_AND_ARGS_FRAME                 // save callee saves
-    movq %gs:THREAD_SELF_OFFSET, %rdx              // pass Thread
-    movq %rsp, %rcx                                // pass SP
-    subq LITERAL(16), %rsp                         // make space for JValue result
-    CFI_ADJUST_CFA_OFFSET(16)
-    movq LITERAL(0), (%rsp)                        // initialize result
-    movq %rsp, %rdi                                // store pointer to JValue result
-    call SYMBOL(artInvokePolymorphic)              // artInvokePolymorphic(result, receiver, Thread*, SP)
+    movq %rsi, %rdi                                // RDI := receiver
+    movq %gs:THREAD_SELF_OFFSET, %rsi              // RSI := Thread (self)
+    movq %rsp, %rdx                                // RDX := pass SP
+    call SYMBOL(artInvokePolymorphic)              // invoke with (receiver, self, SP)
                                                    // save the code pointer
-    subq LITERAL('A'), %rax                        // Convert type descriptor character value to a zero based index.
-    cmpb LITERAL('Z' - 'A'), %al                   // Eliminate out of bounds options
-    ja .Lcleanup_and_return
-    movzbq %al, %rax
-    leaq .Lhandler_table(%rip), %rcx               // Get the address of the handler table
-    movslq (%rcx, %rax, 4), %rax                   // Lookup handler offset relative to table
-    addq %rcx, %rax                                // Add table address to yield handler address.
-    jmpq *%rax                                     // Jump to handler.
-
-.align 4
-.Lhandler_table:                                   // Table of type descriptor to handlers.
-MACRO1(HANDLER_TABLE_OFFSET, handle_label)
-    // NB some tools require 32-bits for relocations. Shouldn't need adjusting.
-    .long RAW_VAR(handle_label) - .Lhandler_table
-END_MACRO
-    HANDLER_TABLE_OFFSET(.Lcleanup_and_return)     // A
-    HANDLER_TABLE_OFFSET(.Lstore_long_result)      // B (byte)
-    HANDLER_TABLE_OFFSET(.Lstore_char_result)      // C (char)
-    HANDLER_TABLE_OFFSET(.Lstore_double_result)    // D (double)
-    HANDLER_TABLE_OFFSET(.Lcleanup_and_return)     // E
-    HANDLER_TABLE_OFFSET(.Lstore_float_result)     // F (float)
-    HANDLER_TABLE_OFFSET(.Lcleanup_and_return)     // G
-    HANDLER_TABLE_OFFSET(.Lcleanup_and_return)     // H
-    HANDLER_TABLE_OFFSET(.Lstore_long_result)      // I (int)
-    HANDLER_TABLE_OFFSET(.Lstore_long_result)      // J (long)
-    HANDLER_TABLE_OFFSET(.Lcleanup_and_return)     // K
-    HANDLER_TABLE_OFFSET(.Lstore_long_result)      // L (object - references are compressed and only 32-bits)
-    HANDLER_TABLE_OFFSET(.Lcleanup_and_return)     // M
-    HANDLER_TABLE_OFFSET(.Lcleanup_and_return)     // N
-    HANDLER_TABLE_OFFSET(.Lcleanup_and_return)     // O
-    HANDLER_TABLE_OFFSET(.Lcleanup_and_return)     // P
-    HANDLER_TABLE_OFFSET(.Lcleanup_and_return)     // Q
-    HANDLER_TABLE_OFFSET(.Lcleanup_and_return)     // R
-    HANDLER_TABLE_OFFSET(.Lstore_long_result)      // S (short)
-    HANDLER_TABLE_OFFSET(.Lcleanup_and_return)     // T
-    HANDLER_TABLE_OFFSET(.Lcleanup_and_return)     // U
-    HANDLER_TABLE_OFFSET(.Lcleanup_and_return)     // V (void)
-    HANDLER_TABLE_OFFSET(.Lcleanup_and_return)     // W
-    HANDLER_TABLE_OFFSET(.Lcleanup_and_return)     // X
-    HANDLER_TABLE_OFFSET(.Lcleanup_and_return)     // Y
-    HANDLER_TABLE_OFFSET(.Lstore_boolean_result)   // Z (boolean)
-
-.Lstore_boolean_result:
-    movzbq (%rsp), %rax                            // Copy boolean result to the accumulator
-    jmp .Lcleanup_and_return
-.Lstore_char_result:
-    movzwq (%rsp), %rax                            // Copy char result to the accumulator
-    jmp .Lcleanup_and_return
-.Lstore_float_result:
-    movd (%rsp), %xmm0                             // Copy float result to the context restored by
-    movd %xmm0, 32(%rsp)                           // RESTORE_SAVE_REFS_AND_ARGS_FRAME.
-    jmp .Lcleanup_and_return
-.Lstore_double_result:
-    movsd (%rsp), %xmm0                            // Copy double result to the context restored by
-    movsd %xmm0, 32(%rsp)                          // RESTORE_SAVE_REFS_AND_ARGS_FRAME.
-    jmp .Lcleanup_and_return
-.Lstore_long_result:
-    movq (%rsp), %rax                              // Copy long result to the accumulator.
-     // Fall-through
-.Lcleanup_and_return:
-    addq LITERAL(16), %rsp                         // Pop space for JValue result.
-    CFI_ADJUST_CFA_OFFSET(16)
     RESTORE_SAVE_REFS_AND_ARGS_FRAME
+    movq %rax, %xmm0                               // Result is in RAX. Copy to FP result register.
     RETURN_OR_DELIVER_PENDING_EXCEPTION
 END_FUNCTION art_quick_invoke_polymorphic
 
+DEFINE_FUNCTION art_quick_invoke_custom
+    SETUP_SAVE_REFS_AND_ARGS_FRAME                 // save callee saves
+                                                   // RDI := call_site_index
+    movq %gs:THREAD_SELF_OFFSET, %rsi              // RSI := Thread::Current()
+    movq %rsp, %rdx                                // RDX := SP
+    call SYMBOL(artInvokeCustom)                   // artInvokeCustom(Thread*, SP)
+    RESTORE_SAVE_REFS_AND_ARGS_FRAME
+    movq %rax, %xmm0                               // Result is in RAX. Copy to FP result register.
+    RETURN_OR_DELIVER_PENDING_EXCEPTION
+END_FUNCTION art_quick_invoke_custom
+
 // Wrap ExecuteSwitchImpl in assembly method which specifies DEX PC for unwinding.
 //  Argument 0: RDI: The context pointer for ExecuteSwitchImpl.
 //  Argument 1: RSI: Pointer to the templated ExecuteSwitchImpl to call.
diff --git a/runtime/art_method.cc b/runtime/art_method.cc
index 45bf664..5b4dcb7 100644
--- a/runtime/art_method.cc
+++ b/runtime/art_method.cc
@@ -425,7 +425,7 @@
 static uint32_t GetOatMethodIndexFromMethodIndex(const DexFile& dex_file,
                                                  uint16_t class_def_idx,
                                                  uint32_t method_idx) {
-  ClassAccessor accessor(dex_file, dex_file.GetClassDef(class_def_idx));
+  ClassAccessor accessor(dex_file, class_def_idx);
   uint32_t class_def_method_index = 0u;
   for (const ClassAccessor::Method& method : accessor.GetMethods()) {
     if (method.GetIndex() == method_idx) {
diff --git a/runtime/asm_support.h b/runtime/asm_support.h
index 70ff40d..ffbff88 100644
--- a/runtime/asm_support.h
+++ b/runtime/asm_support.h
@@ -73,7 +73,7 @@
 
 // Offset of field Thread::tlsPtr_.mterp_current_ibase.
 #define THREAD_CURRENT_IBASE_OFFSET \
-    (THREAD_LOCAL_OBJECTS_OFFSET + __SIZEOF_SIZE_T__ + (1 + 164) * __SIZEOF_POINTER__)
+    (THREAD_LOCAL_OBJECTS_OFFSET + __SIZEOF_SIZE_T__ + (1 + 165) * __SIZEOF_POINTER__)
 ADD_TEST_EQ(THREAD_CURRENT_IBASE_OFFSET,
             art::Thread::MterpCurrentIBaseOffset<POINTER_SIZE>().Int32Value())
 // Offset of field Thread::tlsPtr_.mterp_default_ibase.
diff --git a/runtime/check_reference_map_visitor.h b/runtime/check_reference_map_visitor.h
index acdb235..8a2a70e 100644
--- a/runtime/check_reference_map_visitor.h
+++ b/runtime/check_reference_map_visitor.h
@@ -68,8 +68,8 @@
     StackMap stack_map = code_info.GetStackMapForNativePcOffset(native_pc_offset);
     CodeItemDataAccessor accessor(m->DexInstructionData());
     uint16_t number_of_dex_registers = accessor.RegistersSize();
-    DexRegisterMap dex_register_map =
-        code_info.GetDexRegisterMapOf(stack_map, number_of_dex_registers);
+    DexRegisterMap dex_register_map = code_info.GetDexRegisterMapOf(stack_map);
+    DCHECK_EQ(dex_register_map.size(), number_of_dex_registers);
     uint32_t register_mask = code_info.GetRegisterMaskOf(stack_map);
     BitMemoryRegion stack_mask = code_info.GetStackMaskOf(stack_map);
     for (int i = 0; i < number_of_references; ++i) {
diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc
index be636d8..1710e78 100644
--- a/runtime/class_linker.cc
+++ b/runtime/class_linker.cc
@@ -990,8 +990,7 @@
   class_roots_ = GcRoot<mirror::ObjectArray<mirror::Class>>(
       ObjPtr<mirror::ObjectArray<mirror::Class>>::DownCast(MakeObjPtr(
           spaces[0]->GetImageHeader().GetImageRoot(ImageHeader::kClassRoots))));
-  DCHECK_EQ(GetClassRoot(ClassRoot::kJavaLangClass, this)->GetClassFlags(),
-            mirror::kClassFlagClass);
+  DCHECK_EQ(GetClassRoot<mirror::Class>(this)->GetClassFlags(), mirror::kClassFlagClass);
 
   ObjPtr<mirror::Class> java_lang_Object = GetClassRoot<mirror::Object>(this);
   java_lang_Object->SetObjectSize(sizeof(mirror::Object));
@@ -1610,10 +1609,9 @@
       hs.NewHandle(dex_caches_object->AsObjectArray<mirror::DexCache>()));
   Handle<mirror::ObjectArray<mirror::Class>> class_roots(hs.NewHandle(
       header.GetImageRoot(ImageHeader::kClassRoots)->AsObjectArray<mirror::Class>()));
-  static_assert(ImageHeader::kClassLoader + 1u == ImageHeader::kImageRootsMax,
-                "Class loader should be the last image root.");
   MutableHandle<mirror::ClassLoader> image_class_loader(hs.NewHandle(
-      app_image ? header.GetImageRoot(ImageHeader::kClassLoader)->AsClassLoader() : nullptr));
+      app_image ? header.GetImageRoot(ImageHeader::kAppImageClassLoader)->AsClassLoader()
+                : nullptr));
   DCHECK(class_roots != nullptr);
   if (class_roots->GetLength() != static_cast<int32_t>(ClassRoot::kMax)) {
     *error_msg = StringPrintf("Expected %d class roots but got %d",
@@ -2863,9 +2861,9 @@
   }
 
   const DexFile& dex_file = klass->GetDexFile();
-  const DexFile::ClassDef* dex_class_def = klass->GetClassDef();
-  CHECK(dex_class_def != nullptr);
-  ClassAccessor accessor(dex_file, *dex_class_def);
+  const uint16_t class_def_idx = klass->GetDexClassDefIndex();
+  CHECK_NE(class_def_idx, DexFile::kDexNoIndex16);
+  ClassAccessor accessor(dex_file, class_def_idx);
   // There should always be class data if there were direct methods.
   CHECK(accessor.HasClassData()) << klass->PrettyDescriptor();
   bool has_oat_class;
diff --git a/runtime/dex_register_location.h b/runtime/dex_register_location.h
index c6d4ad2..a20dccb 100644
--- a/runtime/dex_register_location.h
+++ b/runtime/dex_register_location.h
@@ -29,6 +29,7 @@
 class DexRegisterLocation {
  public:
   enum class Kind : int32_t {
+    kInvalid = -2,       // only used internally during register map decoding.
     kNone = -1,          // vreg has not been set.
     kInStack,            // vreg is on the stack, value holds the stack offset.
     kConstant,           // vreg is a constant value.
@@ -40,9 +41,8 @@
 
   DexRegisterLocation(Kind kind, int32_t value) : kind_(kind), value_(value) {}
 
-  static DexRegisterLocation None() {
-    return DexRegisterLocation(Kind::kNone, 0);
-  }
+  static DexRegisterLocation None() { return DexRegisterLocation(Kind::kNone, 0); }
+  static DexRegisterLocation Invalid() { return DexRegisterLocation(Kind::kInvalid, 0); }
 
   bool IsLive() const { return kind_ != Kind::kNone; }
 
diff --git a/runtime/entrypoints/quick/quick_default_externs.h b/runtime/entrypoints/quick/quick_default_externs.h
index 1804d9e..938489b 100644
--- a/runtime/entrypoints/quick/quick_default_externs.h
+++ b/runtime/entrypoints/quick/quick_default_externs.h
@@ -114,9 +114,9 @@
 
 extern "C" void art_quick_invoke_virtual_trampoline_with_access_check(uint32_t, void*);
 
-// Invoke polymorphic entrypoint. Return type is dynamic and may be void, a primitive value, or
-// reference return type.
+// Polymorphic invoke entrypoints.
 extern "C" void art_quick_invoke_polymorphic(uint32_t, void*);
+extern "C" void art_quick_invoke_custom(uint32_t, void*);
 
 // Thread entrypoints.
 extern "C" void art_quick_test_suspend();
diff --git a/runtime/entrypoints/quick/quick_default_init_entrypoints.h b/runtime/entrypoints/quick/quick_default_init_entrypoints.h
index 3f66045..5dcece4 100644
--- a/runtime/entrypoints/quick/quick_default_init_entrypoints.h
+++ b/runtime/entrypoints/quick/quick_default_init_entrypoints.h
@@ -106,6 +106,7 @@
   qpoints->pInvokeVirtualTrampolineWithAccessCheck =
       art_quick_invoke_virtual_trampoline_with_access_check;
   qpoints->pInvokePolymorphic = art_quick_invoke_polymorphic;
+  qpoints->pInvokeCustom = art_quick_invoke_custom;
 
   // Thread
   qpoints->pTestSuspend = art_quick_test_suspend;
diff --git a/runtime/entrypoints/quick/quick_entrypoints_list.h b/runtime/entrypoints/quick/quick_entrypoints_list.h
index 3a8faca..415a158 100644
--- a/runtime/entrypoints/quick/quick_entrypoints_list.h
+++ b/runtime/entrypoints/quick/quick_entrypoints_list.h
@@ -134,6 +134,7 @@
   V(InvokeSuperTrampolineWithAccessCheck, void, uint32_t, void*) \
   V(InvokeVirtualTrampolineWithAccessCheck, void, uint32_t, void*) \
   V(InvokePolymorphic, void, uint32_t, void*) \
+  V(InvokeCustom, void, uint32_t, void*) \
 \
   V(TestSuspend, void, void) \
 \
diff --git a/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc b/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc
index af6a936..2b1623a 100644
--- a/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc
+++ b/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc
@@ -35,6 +35,7 @@
 #include "index_bss_mapping.h"
 #include "instrumentation.h"
 #include "interpreter/interpreter.h"
+#include "interpreter/interpreter_common.h"
 #include "interpreter/shadow_frame-inl.h"
 #include "jit/jit.h"
 #include "linear_alloc.h"
@@ -2722,18 +2723,11 @@
                                 reinterpret_cast<uintptr_t>(method));
 }
 
-// Returns shorty type so the caller can determine how to put |result|
-// into expected registers. The shorty type is static so the compiler
-// could call different flavors of this code path depending on the
-// shorty type though this would require different entry points for
-// each type.
-extern "C" uintptr_t artInvokePolymorphic(
-    JValue* result,
-    mirror::Object* raw_receiver,
-    Thread* self,
-    ArtMethod** sp)
+// Returns uint64_t representing raw bits from JValue.
+extern "C" uint64_t artInvokePolymorphic(mirror::Object* raw_receiver, Thread* self, ArtMethod** sp)
     REQUIRES_SHARED(Locks::mutator_lock_) {
   ScopedQuickEntrypointChecks sqec(self);
+  DCHECK(raw_receiver != nullptr);
   DCHECK_EQ(*sp, Runtime::Current()->GetCalleeSaveMethod(CalleeSaveType::kSaveRefsAndArgs));
 
   // Start new JNI local reference state
@@ -2766,18 +2760,12 @@
   ArtMethod* resolved_method = linker->ResolveMethod<ClassLinker::ResolveMode::kCheckICCEAndIAE>(
       self, inst.VRegB(), caller_method, kVirtual);
 
-  if (UNLIKELY(receiver_handle.IsNull())) {
-    ThrowNullPointerExceptionForMethodAccess(resolved_method, InvokeType::kVirtual);
-    return static_cast<uintptr_t>('V');
-  }
-
   Handle<mirror::MethodType> method_type(
       hs.NewHandle(linker->ResolveMethodType(self, proto_idx, caller_method)));
-
-  // This implies we couldn't resolve one or more types in this method handle.
   if (UNLIKELY(method_type.IsNull())) {
+    // This implies we couldn't resolve one or more types in this method handle.
     CHECK(self->IsExceptionPending());
-    return static_cast<uintptr_t>('V');
+    return 0UL;
   }
 
   DCHECK_EQ(ArtMethod::NumArgRegisters(shorty) + 1u, (uint32_t)inst.VRegA());
@@ -2811,6 +2799,7 @@
   // consecutive order.
   RangeInstructionOperands operands(first_arg + 1, num_vregs - 1);
   Intrinsics intrinsic = static_cast<Intrinsics>(resolved_method->GetIntrinsic());
+  JValue result;
   bool success = false;
   if (resolved_method->GetDeclaringClass() == GetClassRoot<mirror::MethodHandle>(linker)) {
     Handle<mirror::MethodHandle> method_handle(hs.NewHandle(
@@ -2821,7 +2810,7 @@
                                         method_handle,
                                         method_type,
                                         &operands,
-                                        result);
+                                        &result);
     } else {
       DCHECK_EQ(static_cast<uint32_t>(intrinsic),
                 static_cast<uint32_t>(Intrinsics::kMethodHandleInvoke));
@@ -2830,7 +2819,7 @@
                                    method_handle,
                                    method_type,
                                    &operands,
-                                   result);
+                                   &result);
     }
   } else {
     DCHECK_EQ(GetClassRoot<mirror::VarHandle>(linker), resolved_method->GetDeclaringClass());
@@ -2844,7 +2833,7 @@
                                       method_type,
                                       access_mode,
                                       &operands,
-                                      result);
+                                      &result);
   }
 
   DCHECK(success || self->IsExceptionPending());
@@ -2852,7 +2841,65 @@
   // Pop transition record.
   self->PopManagedStackFragment(fragment);
 
-  return static_cast<uintptr_t>(shorty[0]);
+  return result.GetJ();
+}
+
+// Returns uint64_t representing raw bits from JValue.
+extern "C" uint64_t artInvokeCustom(uint32_t call_site_idx, Thread* self, ArtMethod** sp)
+    REQUIRES_SHARED(Locks::mutator_lock_) {
+  ScopedQuickEntrypointChecks sqec(self);
+  DCHECK_EQ(*sp, Runtime::Current()->GetCalleeSaveMethod(CalleeSaveType::kSaveRefsAndArgs));
+
+  // invoke-custom is effectively a static call (no receiver).
+  static constexpr bool kMethodIsStatic = true;
+
+  // Start new JNI local reference state
+  JNIEnvExt* env = self->GetJniEnv();
+  ScopedObjectAccessUnchecked soa(env);
+  ScopedJniEnvLocalRefState env_state(env);
+
+  const char* old_cause = self->StartAssertNoThreadSuspension("Making stack arguments safe.");
+
+  // From the instruction, get the |callsite_shorty| and expose arguments on the stack to the GC.
+  ArtMethod* caller_method = QuickArgumentVisitor::GetCallingMethod(sp);
+  uint32_t dex_pc = QuickArgumentVisitor::GetCallingDexPc(sp);
+  const DexFile* dex_file = caller_method->GetDexFile();
+  const dex::ProtoIndex proto_idx(dex_file->GetProtoIndexForCallSite(call_site_idx));
+  const char* shorty = caller_method->GetDexFile()->GetShorty(proto_idx);
+  const uint32_t shorty_len = strlen(shorty);
+
+  // Construct the shadow frame placing arguments consecutively from |first_arg|.
+  const size_t first_arg = 0;
+  const size_t num_vregs = ArtMethod::NumArgRegisters(shorty);
+  ShadowFrameAllocaUniquePtr shadow_frame_unique_ptr =
+      CREATE_SHADOW_FRAME(num_vregs, /* link */ nullptr, caller_method, dex_pc);
+  ShadowFrame* shadow_frame = shadow_frame_unique_ptr.get();
+  ScopedStackedShadowFramePusher
+      frame_pusher(self, shadow_frame, StackedShadowFrameType::kShadowFrameUnderConstruction);
+  BuildQuickShadowFrameVisitor shadow_frame_builder(sp,
+                                                    kMethodIsStatic,
+                                                    shorty,
+                                                    shorty_len,
+                                                    shadow_frame,
+                                                    first_arg);
+  shadow_frame_builder.VisitArguments();
+
+  // Push a transition back into managed code onto the linked list in thread.
+  ManagedStack fragment;
+  self->PushManagedStackFragment(&fragment);
+  self->EndAssertNoThreadSuspension(old_cause);
+
+  // Perform the invoke-custom operation.
+  RangeInstructionOperands operands(first_arg, num_vregs);
+  JValue result;
+  bool success =
+      interpreter::DoInvokeCustom(self, *shadow_frame, call_site_idx, &operands, &result);
+  DCHECK(success || self->IsExceptionPending());
+
+  // Pop transition record.
+  self->PopManagedStackFragment(fragment);
+
+  return result.GetJ();
 }
 
 }  // namespace art
diff --git a/runtime/entrypoints_order_test.cc b/runtime/entrypoints_order_test.cc
index 1337cd5..dda3dde 100644
--- a/runtime/entrypoints_order_test.cc
+++ b/runtime/entrypoints_order_test.cc
@@ -287,8 +287,8 @@
                          pInvokeVirtualTrampolineWithAccessCheck, sizeof(void*));
     EXPECT_OFFSET_DIFFNP(QuickEntryPoints, pInvokeVirtualTrampolineWithAccessCheck,
                          pInvokePolymorphic, sizeof(void*));
-    EXPECT_OFFSET_DIFFNP(QuickEntryPoints, pInvokePolymorphic,
-                         pTestSuspend, sizeof(void*));
+    EXPECT_OFFSET_DIFFNP(QuickEntryPoints, pInvokePolymorphic, pInvokeCustom, sizeof(void*));
+    EXPECT_OFFSET_DIFFNP(QuickEntryPoints, pInvokeCustom, pTestSuspend, sizeof(void*));
     EXPECT_OFFSET_DIFFNP(QuickEntryPoints, pTestSuspend, pDeliverException, sizeof(void*));
 
     EXPECT_OFFSET_DIFFNP(QuickEntryPoints, pDeliverException, pThrowArrayBounds, sizeof(void*));
diff --git a/runtime/image.cc b/runtime/image.cc
index 17fc664..7819c0b 100644
--- a/runtime/image.cc
+++ b/runtime/image.cc
@@ -26,7 +26,7 @@
 namespace art {
 
 const uint8_t ImageHeader::kImageMagic[] = { 'a', 'r', 't', '\n' };
-const uint8_t ImageHeader::kImageVersion[] = { '0', '6', '1', '\0' };  // Pre-allocated Throwables.
+const uint8_t ImageHeader::kImageVersion[] = { '0', '6', '2', '\0' };  // Boot image live objects.
 
 ImageHeader::ImageHeader(uint32_t image_begin,
                          uint32_t image_size,
diff --git a/runtime/image.h b/runtime/image.h
index c6fc052..c1cde0a 100644
--- a/runtime/image.h
+++ b/runtime/image.h
@@ -211,8 +211,12 @@
     kOomeWhenThrowingOome,            // Pre-allocated OOME when throwing OOME.
     kOomeWhenHandlingStackOverflow,   // Pre-allocated OOME when handling StackOverflowError.
     kNoClassDefFoundError,            // Pre-allocated NoClassDefFoundError.
-    kClassLoader,                     // App image only.
+    kSpecialRoots,                    // Different for boot image and app image, see aliases below.
     kImageRootsMax,
+
+    // Aliases.
+    kAppImageClassLoader = kSpecialRoots,   // The class loader used to build the app image.
+    kBootImageLiveObjects = kSpecialRoots,  // Array of boot image objects that must be kept live.
   };
 
   enum ImageSections {
@@ -229,8 +233,10 @@
     kSectionCount,  // Number of elements in enum.
   };
 
-  static size_t NumberOfImageRoots(bool app_image) {
-    return app_image ? kImageRootsMax : kImageRootsMax - 1u;
+  static size_t NumberOfImageRoots(bool app_image ATTRIBUTE_UNUSED) {
+    // At the moment, boot image and app image have the same number of roots,
+    // though the meaning of the kSpecialRoots is different.
+    return kImageRootsMax;
   }
 
   ArtMethod* GetImageMethod(ImageMethod index) const;
diff --git a/runtime/interpreter/interpreter_common.cc b/runtime/interpreter/interpreter_common.cc
index 27f761a..92d4731 100644
--- a/runtime/interpreter/interpreter_common.cc
+++ b/runtime/interpreter/interpreter_common.cc
@@ -1164,7 +1164,7 @@
                                                       ShadowFrame& shadow_frame,
                                                       uint32_t call_site_idx)
     REQUIRES_SHARED(Locks::mutator_lock_) {
-  StackHandleScope<7> hs(self);
+  StackHandleScope<5> hs(self);
   // There are three mandatory arguments expected from the call site
   // value array in the DEX file: the bootstrap method handle, the
   // method name to pass to the bootstrap method, and the method type
@@ -1358,75 +1358,80 @@
   }
 
   // Check the call site target is not null as we're going to invoke it.
-  Handle<mirror::CallSite> call_site =
-      hs.NewHandle(ObjPtr<mirror::CallSite>::DownCast(ObjPtr<mirror::Object>(result.GetL())));
-  Handle<mirror::MethodHandle> target = hs.NewHandle(call_site->GetTarget());
-  if (UNLIKELY(target.IsNull())) {
+  ObjPtr<mirror::CallSite> call_site =
+      ObjPtr<mirror::CallSite>::DownCast(ObjPtr<mirror::Object>(result.GetL()));
+  ObjPtr<mirror::MethodHandle> target = call_site->GetTarget();
+  if (UNLIKELY(target == nullptr)) {
     ThrowClassCastException("Bootstrap method returned a CallSite with a null target");
     return nullptr;
   }
-  return call_site.Get();
+  return call_site;
 }
 
-template<bool is_range>
+namespace {
+
+ObjPtr<mirror::CallSite> DoResolveCallSite(Thread* self,
+                                           ShadowFrame& shadow_frame,
+                                           uint32_t call_site_idx)
+    REQUIRES_SHARED(Locks::mutator_lock_) {
+  StackHandleScope<1> hs(self);
+  Handle<mirror::DexCache> dex_cache(hs.NewHandle(shadow_frame.GetMethod()->GetDexCache()));
+
+  // Get the call site from the DexCache if present.
+  ObjPtr<mirror::CallSite> call_site = dex_cache->GetResolvedCallSite(call_site_idx);
+  if (LIKELY(call_site != nullptr)) {
+    return call_site;
+  }
+
+  // Invoke the bootstrap method to get a candidate call site.
+  call_site = InvokeBootstrapMethod(self, shadow_frame, call_site_idx);
+  if (UNLIKELY(call_site == nullptr)) {
+    if (!self->GetException()->IsError()) {
+      // Use a BootstrapMethodError if the exception is not an instance of java.lang.Error.
+      ThrowWrappedBootstrapMethodError("Exception from call site #%u bootstrap method",
+                                       call_site_idx);
+    }
+    return nullptr;
+  }
+
+  // Attempt to place the candidate call site into the DexCache, return the winning call site.
+  return dex_cache->SetResolvedCallSite(call_site_idx, call_site);
+}
+
+}  // namespace
+
 bool DoInvokeCustom(Thread* self,
                     ShadowFrame& shadow_frame,
-                    const Instruction* inst,
-                    uint16_t inst_data,
-                    JValue* result)
-    REQUIRES_SHARED(Locks::mutator_lock_) {
+                    uint32_t call_site_idx,
+                    const InstructionOperands* operands,
+                    JValue* result) {
   // Make sure to check for async exceptions
   if (UNLIKELY(self->ObserveAsyncException())) {
     return false;
   }
+
   // invoke-custom is not supported in transactions. In transactions
   // there is a limited set of types supported. invoke-custom allows
   // running arbitrary code and instantiating arbitrary types.
   CHECK(!Runtime::Current()->IsActiveTransaction());
-  StackHandleScope<4> hs(self);
-  Handle<mirror::DexCache> dex_cache(hs.NewHandle(shadow_frame.GetMethod()->GetDexCache()));
-  const uint32_t call_site_idx = is_range ? inst->VRegB_3rc() : inst->VRegB_35c();
-  MutableHandle<mirror::CallSite>
-      call_site(hs.NewHandle(dex_cache->GetResolvedCallSite(call_site_idx)));
+
+  ObjPtr<mirror::CallSite> call_site = DoResolveCallSite(self, shadow_frame, call_site_idx);
   if (call_site.IsNull()) {
-    call_site.Assign(InvokeBootstrapMethod(self, shadow_frame, call_site_idx));
-    if (UNLIKELY(call_site.IsNull())) {
-      CHECK(self->IsExceptionPending());
-      if (!self->GetException()->IsError()) {
-        // Use a BootstrapMethodError if the exception is not an instance of java.lang.Error.
-        ThrowWrappedBootstrapMethodError("Exception from call site #%u bootstrap method",
-                                         call_site_idx);
-      }
-      result->SetJ(0);
-      return false;
-    }
-    mirror::CallSite* winning_call_site =
-        dex_cache->SetResolvedCallSite(call_site_idx, call_site.Get());
-    call_site.Assign(winning_call_site);
+    DCHECK(self->IsExceptionPending());
+    return false;
   }
 
+  StackHandleScope<2> hs(self);
   Handle<mirror::MethodHandle> target = hs.NewHandle(call_site->GetTarget());
   Handle<mirror::MethodType> target_method_type = hs.NewHandle(target->GetMethodType());
-  DCHECK_EQ(static_cast<size_t>(inst->VRegA()), target_method_type->NumberOfVRegs());
-  if (is_range) {
-    RangeInstructionOperands operands(inst->VRegC_3rc(), inst->VRegA_3rc());
-    return MethodHandleInvokeExact(self,
-                                   shadow_frame,
-                                   target,
-                                   target_method_type,
-                                   &operands,
-                                   result);
-  } else {
-    uint32_t args[Instruction::kMaxVarArgRegs];
-    inst->GetVarArgs(args, inst_data);
-    VarArgsInstructionOperands operands(args, inst->VRegA_35c());
-    return MethodHandleInvokeExact(self,
-                                   shadow_frame,
-                                   target,
-                                   target_method_type,
-                                   &operands,
-                                   result);
-  }
+  DCHECK_EQ(operands->GetNumberOfOperands(), target_method_type->NumberOfVRegs())
+      << " call_site_idx" << call_site_idx;
+  return MethodHandleInvokeExact(self,
+                                 shadow_frame,
+                                 target,
+                                 target_method_type,
+                                 operands,
+                                 result);
 }
 
 // Assign register 'src_reg' from shadow_frame to register 'dest_reg' into new_shadow_frame.
@@ -1847,16 +1852,6 @@
 EXPLICIT_DO_INVOKE_POLYMORPHIC_TEMPLATE_DECL(true);
 #undef EXPLICIT_DO_INVOKE_POLYMORPHIC_TEMPLATE_DECL
 
-// Explicit DoInvokeCustom template function declarations.
-#define EXPLICIT_DO_INVOKE_CUSTOM_TEMPLATE_DECL(_is_range)               \
-  template REQUIRES_SHARED(Locks::mutator_lock_)                         \
-  bool DoInvokeCustom<_is_range>(                                        \
-      Thread* self, ShadowFrame& shadow_frame, const Instruction* inst,  \
-      uint16_t inst_data, JValue* result)
-EXPLICIT_DO_INVOKE_CUSTOM_TEMPLATE_DECL(false);
-EXPLICIT_DO_INVOKE_CUSTOM_TEMPLATE_DECL(true);
-#undef EXPLICIT_DO_INVOKE_CUSTOM_TEMPLATE_DECL
-
 // Explicit DoFilledNewArray template function declarations.
 #define EXPLICIT_DO_FILLED_NEW_ARRAY_TEMPLATE_DECL(_is_range_, _check, _transaction_active)       \
   template REQUIRES_SHARED(Locks::mutator_lock_)                                                  \
diff --git a/runtime/interpreter/interpreter_common.h b/runtime/interpreter/interpreter_common.h
index 60bf505..b324b4c 100644
--- a/runtime/interpreter/interpreter_common.h
+++ b/runtime/interpreter/interpreter_common.h
@@ -242,7 +242,15 @@
                          ShadowFrame& shadow_frame,
                          const Instruction* inst,
                          uint16_t inst_data,
-                         JValue* result);
+                         JValue* result)
+    REQUIRES_SHARED(Locks::mutator_lock_);
+
+bool DoInvokeCustom(Thread* self,
+                    ShadowFrame& shadow_frame,
+                    uint32_t call_site_idx,
+                    const InstructionOperands* operands,
+                    JValue* result)
+    REQUIRES_SHARED(Locks::mutator_lock_);
 
 // Performs a custom invoke (invoke-custom/invoke-custom-range).
 template<bool is_range>
@@ -250,7 +258,19 @@
                     ShadowFrame& shadow_frame,
                     const Instruction* inst,
                     uint16_t inst_data,
-                    JValue* result);
+                    JValue* result)
+    REQUIRES_SHARED(Locks::mutator_lock_) {
+  const uint32_t call_site_idx = is_range ? inst->VRegB_3rc() : inst->VRegB_35c();
+  if (is_range) {
+    RangeInstructionOperands operands(inst->VRegC_3rc(), inst->VRegA_3rc());
+    return DoInvokeCustom(self, shadow_frame, call_site_idx, &operands, result);
+  } else {
+    uint32_t args[Instruction::kMaxVarArgRegs];
+    inst->GetVarArgs(args, inst_data);
+    VarArgsInstructionOperands operands(args, inst->VRegA_35c());
+    return DoInvokeCustom(self, shadow_frame, call_site_idx, &operands, result);
+  }
+}
 
 // Handles invoke-virtual-quick and invoke-virtual-quick-range instructions.
 // Returns true on success, otherwise throws an exception and returns false.
diff --git a/runtime/jit/jit.cc b/runtime/jit/jit.cc
index f31a24e..0a8e0cd 100644
--- a/runtime/jit/jit.cc
+++ b/runtime/jit/jit.cc
@@ -493,8 +493,7 @@
 
     // We found a stack map, now fill the frame with dex register values from the interpreter's
     // shadow frame.
-    DexRegisterMap vreg_map =
-        code_info.GetDexRegisterMapOf(stack_map, number_of_vregs);
+    DexRegisterMap vreg_map = code_info.GetDexRegisterMapOf(stack_map);
 
     frame_size = osr_method->GetFrameSizeInBytes();
 
@@ -510,10 +509,11 @@
     memory[0] = method;
 
     shadow_frame = thread->PopShadowFrame();
-    if (!vreg_map.IsValid()) {
+    if (vreg_map.empty()) {
       // If we don't have a dex register map, then there are no live dex registers at
       // this dex pc.
     } else {
+      DCHECK_EQ(vreg_map.size(), number_of_vregs);
       for (uint16_t vreg = 0; vreg < number_of_vregs; ++vreg) {
         DexRegisterLocation::Kind location = vreg_map.GetLocationKind(vreg);
         if (location == DexRegisterLocation::Kind::kNone) {
diff --git a/runtime/jni/java_vm_ext.cc b/runtime/jni/java_vm_ext.cc
index 8fe68bd..44679a5 100644
--- a/runtime/jni/java_vm_ext.cc
+++ b/runtime/jni/java_vm_ext.cc
@@ -912,7 +912,11 @@
               return utf.c_str();
             }
           }
-          env->ExceptionClear();
+          if (env->ExceptionCheck()) {
+            // We can't do much better logging, really. So leave it with a Describe.
+            env->ExceptionDescribe();
+            env->ExceptionClear();
+          }
           return "(Error calling toString)";
         }
         return "null";
diff --git a/runtime/mirror/dex_cache-inl.h b/runtime/mirror/dex_cache-inl.h
index 96778aa..e64a325 100644
--- a/runtime/mirror/dex_cache-inl.h
+++ b/runtime/mirror/dex_cache-inl.h
@@ -157,7 +157,8 @@
   return ref.load(std::memory_order_seq_cst).Read();
 }
 
-inline CallSite* DexCache::SetResolvedCallSite(uint32_t call_site_idx, CallSite* call_site) {
+inline ObjPtr<CallSite> DexCache::SetResolvedCallSite(uint32_t call_site_idx,
+                                                      ObjPtr<CallSite> call_site) {
   DCHECK(Runtime::Current()->IsMethodHandlesEnabled());
   DCHECK_LT(call_site_idx, GetDexFile()->NumCallSiteIds());
 
diff --git a/runtime/mirror/dex_cache.h b/runtime/mirror/dex_cache.h
index bb86004..941248e 100644
--- a/runtime/mirror/dex_cache.h
+++ b/runtime/mirror/dex_cache.h
@@ -320,8 +320,8 @@
   // because multiple threads can invoke the bootstrap method each
   // producing a call site, but the method handle invocation on the
   // call site must be on a common agreed value.
-  CallSite* SetResolvedCallSite(uint32_t call_site_idx, CallSite* resolved) WARN_UNUSED
-      REQUIRES_SHARED(Locks::mutator_lock_);
+  ObjPtr<CallSite> SetResolvedCallSite(uint32_t call_site_idx, ObjPtr<CallSite> resolved)
+      REQUIRES_SHARED(Locks::mutator_lock_) WARN_UNUSED;
 
   StringDexCacheType* GetStrings() ALWAYS_INLINE REQUIRES_SHARED(Locks::mutator_lock_) {
     return GetFieldPtr64<StringDexCacheType*>(StringsOffset());
diff --git a/runtime/oat.h b/runtime/oat.h
index 72eb27d..40f4edd 100644
--- a/runtime/oat.h
+++ b/runtime/oat.h
@@ -32,8 +32,8 @@
 class PACKED(4) OatHeader {
  public:
   static constexpr uint8_t kOatMagic[] = { 'o', 'a', 't', '\n' };
-  // Last oat version changed reason: Rewrite TypeLookupTable.
-  static constexpr uint8_t kOatVersion[] = { '1', '4', '7', '\0' };
+  // Last oat version changed reason: compiler support invoke-custom
+  static constexpr uint8_t kOatVersion[] = { '1', '4', '8', '\0' };
 
   static constexpr const char* kImageLocationKey = "image-location";
   static constexpr const char* kDex2OatCmdLineKey = "dex2oat-cmdline";
diff --git a/runtime/quick_exception_handler.cc b/runtime/quick_exception_handler.cc
index 23ccf6a..cf1cbe7 100644
--- a/runtime/quick_exception_handler.cc
+++ b/runtime/quick_exception_handler.cc
@@ -230,19 +230,18 @@
   // Find stack map of the catch block.
   StackMap catch_stack_map = code_info.GetCatchStackMapForDexPc(GetHandlerDexPc());
   DCHECK(catch_stack_map.IsValid());
-  DexRegisterMap catch_vreg_map =
-      code_info.GetDexRegisterMapOf(catch_stack_map, number_of_vregs);
-  if (!catch_vreg_map.IsValid() || !catch_vreg_map.HasAnyLiveDexRegisters()) {
+  DexRegisterMap catch_vreg_map = code_info.GetDexRegisterMapOf(catch_stack_map);
+  if (!catch_vreg_map.HasAnyLiveDexRegisters()) {
     return;
   }
+  DCHECK_EQ(catch_vreg_map.size(), number_of_vregs);
 
   // Find stack map of the throwing instruction.
   StackMap throw_stack_map =
       code_info.GetStackMapForNativePcOffset(stack_visitor->GetNativePcOffset());
   DCHECK(throw_stack_map.IsValid());
-  DexRegisterMap throw_vreg_map =
-      code_info.GetDexRegisterMapOf(throw_stack_map, number_of_vregs);
-  DCHECK(throw_vreg_map.IsValid());
+  DexRegisterMap throw_vreg_map = code_info.GetDexRegisterMapOf(throw_stack_map);
+  DCHECK_EQ(throw_vreg_map.size(), number_of_vregs);
 
   // Copy values between them.
   for (uint16_t vreg = 0; vreg < number_of_vregs; ++vreg) {
@@ -405,14 +404,12 @@
     uint32_t register_mask = code_info.GetRegisterMaskOf(stack_map);
     BitMemoryRegion stack_mask = code_info.GetStackMaskOf(stack_map);
     DexRegisterMap vreg_map = IsInInlinedFrame()
-        ? code_info.GetDexRegisterMapAtDepth(GetCurrentInliningDepth() - 1,
-                                             stack_map,
-                                             number_of_vregs)
-        : code_info.GetDexRegisterMapOf(stack_map, number_of_vregs);
-
-    if (!vreg_map.IsValid()) {
+        ? code_info.GetDexRegisterMapAtDepth(GetCurrentInliningDepth() - 1, stack_map)
+        : code_info.GetDexRegisterMapOf(stack_map);
+    if (vreg_map.empty()) {
       return;
     }
+    DCHECK_EQ(vreg_map.size(), number_of_vregs);
 
     for (uint16_t vreg = 0; vreg < number_of_vregs; ++vreg) {
       if (updated_vregs != nullptr && updated_vregs[vreg]) {
diff --git a/runtime/runtime.cc b/runtime/runtime.cc
index 6d10a22..a1645cb 100644
--- a/runtime/runtime.cc
+++ b/runtime/runtime.cc
@@ -2045,6 +2045,7 @@
   pre_allocated_OutOfMemoryError_when_handling_stack_overflow_
       .VisitRootIfNonNull(visitor, RootInfo(kRootVMInternal));
   pre_allocated_NoClassDefFoundError_.VisitRootIfNonNull(visitor, RootInfo(kRootVMInternal));
+  VisitImageRoots(visitor);
   verifier::MethodVerifier::VisitStaticRoots(visitor);
   VisitTransactionRoots(visitor);
 }
diff --git a/runtime/stack.cc b/runtime/stack.cc
index bd0d5d6..56e47b9 100644
--- a/runtime/stack.cc
+++ b/runtime/stack.cc
@@ -236,12 +236,12 @@
   size_t depth_in_stack_map = current_inlining_depth_ - 1;
 
   DexRegisterMap dex_register_map = IsInInlinedFrame()
-      ? code_info.GetDexRegisterMapAtDepth(depth_in_stack_map, stack_map, number_of_dex_registers)
-      : code_info.GetDexRegisterMapOf(stack_map, number_of_dex_registers);
-
-  if (!dex_register_map.IsValid()) {
+      ? code_info.GetDexRegisterMapAtDepth(depth_in_stack_map, stack_map)
+      : code_info.GetDexRegisterMapOf(stack_map);
+  if (dex_register_map.empty()) {
     return false;
   }
+  DCHECK_EQ(dex_register_map.size(), number_of_dex_registers);
   DexRegisterLocation::Kind location_kind = dex_register_map.GetLocationKind(vreg);
   switch (location_kind) {
     case DexRegisterLocation::Kind::kInStack: {
diff --git a/runtime/stack_map.cc b/runtime/stack_map.cc
index a5749b8..a25c9fd 100644
--- a/runtime/stack_map.cc
+++ b/runtime/stack_map.cc
@@ -25,6 +25,69 @@
 
 namespace art {
 
+// Scan backward to determine dex register locations at given stack map.
+// All registers for a stack map are combined - inlined registers are just appended,
+// therefore 'first_dex_register' allows us to select a sub-range to decode.
+void CodeInfo::DecodeDexRegisterMap(uint32_t stack_map_index,
+                                    uint32_t first_dex_register,
+                                    /*out*/ DexRegisterMap* map) const {
+  // Count remaining work so we know when we have finished.
+  uint32_t remaining_registers = map->size();
+
+  // Keep scanning backwards and collect the most recent location of each register.
+  for (int32_t s = stack_map_index; s >= 0 && remaining_registers != 0; s--) {
+    StackMap stack_map = GetStackMapAt(s);
+    DCHECK_LE(stack_map_index - s, kMaxDexRegisterMapSearchDistance) << "Unbounded search";
+
+    // The mask specifies which registers where modified in this stack map.
+    // NB: the mask can be shorter than expected if trailing zero bits were removed.
+    uint32_t mask_index = stack_map.GetDexRegisterMaskIndex();
+    if (mask_index == StackMap::kNoValue) {
+      continue;  // Nothing changed at this stack map.
+    }
+    BitMemoryRegion mask = dex_register_masks_.GetBitMemoryRegion(mask_index);
+    if (mask.size_in_bits() <= first_dex_register) {
+      continue;  // Nothing changed after the first register we are interested in.
+    }
+
+    // The map stores one catalogue index per each modified register location.
+    uint32_t map_index = stack_map.GetDexRegisterMapIndex();
+    DCHECK_NE(map_index, StackMap::kNoValue);
+
+    // Skip initial registers which we are not interested in (to get to inlined registers).
+    map_index += mask.PopCount(0, first_dex_register);
+    mask = mask.Subregion(first_dex_register, mask.size_in_bits() - first_dex_register);
+
+    // Update registers that we see for first time (i.e. most recent value).
+    DexRegisterLocation* regs = map->data();
+    const uint32_t end = std::min<uint32_t>(map->size(), mask.size_in_bits());
+    const size_t kNumBits = BitSizeOf<uint32_t>();
+    for (uint32_t reg = 0; reg < end; reg += kNumBits) {
+      // Process the mask in chunks of kNumBits for performance.
+      uint32_t bits = mask.LoadBits(reg, std::min<uint32_t>(end - reg, kNumBits));
+      while (bits != 0) {
+        uint32_t bit = CTZ(bits);
+        if (regs[reg + bit].GetKind() == DexRegisterLocation::Kind::kInvalid) {
+          regs[reg + bit] = GetDexRegisterCatalogEntry(dex_register_maps_.Get(map_index));
+          remaining_registers--;
+        }
+        map_index++;
+        bits ^= 1u << bit;  // Clear the bit.
+      }
+    }
+  }
+
+  // Set any remaining registers to None (which is the default state at first stack map).
+  if (remaining_registers != 0) {
+    DexRegisterLocation* regs = map->data();
+    for (uint32_t r = 0; r < map->size(); r++) {
+      if (regs[r].GetKind() == DexRegisterLocation::Kind::kInvalid) {
+        regs[r] = DexRegisterLocation::None();
+      }
+    }
+  }
+}
+
 std::ostream& operator<<(std::ostream& stream, const DexRegisterLocation& reg) {
   using Kind = DexRegisterLocation::Kind;
   switch (reg.GetKind()) {
@@ -42,6 +105,8 @@
       return stream << "f" << reg.GetValue() << "/hi";
     case Kind::kConstant:
       return stream << "#" << reg.GetValue();
+    case Kind::kInvalid:
+      return stream << "Invalid";
     default:
       return stream << "DexRegisterLocation(" << static_cast<uint32_t>(reg.GetKind())
                     << "," << reg.GetValue() << ")";
@@ -50,7 +115,7 @@
 
 static void DumpDexRegisterMap(VariableIndentationOutputStream* vios,
                                const DexRegisterMap& map) {
-  if (map.IsValid()) {
+  if (!map.empty()) {
     ScopedIndentation indent1(vios);
     for (size_t i = 0; i < map.size(); ++i) {
       if (map.IsDexRegisterLive(i)) {
@@ -98,7 +163,6 @@
 
 void CodeInfo::Dump(VariableIndentationOutputStream* vios,
                     uint32_t code_offset,
-                    uint16_t num_dex_registers,
                     bool verbose,
                     InstructionSet instruction_set,
                     const MethodInfo& method_info) const {
@@ -120,7 +184,7 @@
   if (verbose) {
     for (size_t i = 0; i < GetNumberOfStackMaps(); ++i) {
       StackMap stack_map = GetStackMapAt(i);
-      stack_map.Dump(vios, *this, method_info, code_offset, num_dex_registers, instruction_set);
+      stack_map.Dump(vios, *this, method_info, code_offset, instruction_set);
     }
   }
 }
@@ -129,7 +193,6 @@
                     const CodeInfo& code_info,
                     const MethodInfo& method_info,
                     uint32_t code_offset,
-                    uint16_t number_of_dex_registers,
                     InstructionSet instruction_set) const {
   const uint32_t pc_offset = GetNativePcOffset(instruction_set);
   vios->Stream()
@@ -145,22 +208,18 @@
     vios->Stream() << stack_mask.LoadBit(e - i - 1);
   }
   vios->Stream() << ")\n";
-  DumpDexRegisterMap(vios, code_info.GetDexRegisterMapOf(*this, number_of_dex_registers));
+  DumpDexRegisterMap(vios, code_info.GetDexRegisterMapOf(*this));
   uint32_t depth = code_info.GetInlineDepthOf(*this);
   for (size_t d = 0; d < depth; d++) {
     InlineInfo inline_info = code_info.GetInlineInfoAtDepth(*this, d);
-    // We do not know the length of the dex register maps of inlined frames
-    // at this level, so we just pass null to `InlineInfo::Dump` to tell
-    // it not to look at these maps.
-    inline_info.Dump(vios, code_info, *this, method_info, 0);
+    inline_info.Dump(vios, code_info, *this, method_info);
   }
 }
 
 void InlineInfo::Dump(VariableIndentationOutputStream* vios,
                       const CodeInfo& code_info,
                       const StackMap& stack_map,
-                      const MethodInfo& method_info,
-                      uint16_t number_of_dex_registers) const {
+                      const MethodInfo& method_info) const {
   uint32_t depth = Row() - stack_map.GetInlineInfoIndex();
   vios->Stream()
       << "InlineInfo[" << Row() << "]"
@@ -176,10 +235,7 @@
         << ", method_index=" << GetMethodIndex(method_info);
   }
   vios->Stream() << ")\n";
-  if (number_of_dex_registers != 0) {
-    uint16_t vregs = number_of_dex_registers;
-    DumpDexRegisterMap(vios, code_info.GetDexRegisterMapAtDepth(depth, stack_map, vregs));
-  }
+  DumpDexRegisterMap(vios, code_info.GetDexRegisterMapAtDepth(depth, stack_map));
 }
 
 }  // namespace art
diff --git a/runtime/stack_map.h b/runtime/stack_map.h
index b04197e..53f80e5 100644
--- a/runtime/stack_map.h
+++ b/runtime/stack_map.h
@@ -39,6 +39,12 @@
 // (signed) values.
 static constexpr ssize_t kFrameSlotSize = 4;
 
+// The delta compression of dex register maps means we need to scan the stackmaps backwards.
+// We compress the data in such a way so that there is an upper bound on the search distance.
+// Max distance 0 means each stack map must be fully defined and no scanning back is allowed.
+// If this value is changed, the oat file version should be incremented (for DCHECK to pass).
+static constexpr size_t kMaxDexRegisterMapSearchDistance = 32;
+
 class ArtMethod;
 class CodeInfo;
 
@@ -49,12 +55,14 @@
 // If the size is small enough, it keeps the data on the stack.
 class DexRegisterMap {
  public:
-  // Create map for given number of registers and initialize all locations to None.
-  explicit DexRegisterMap(size_t count) : count_(count), regs_small_{} {
+  using iterator = DexRegisterLocation*;
+
+  // Create map for given number of registers and initialize them to the given value.
+  DexRegisterMap(size_t count, DexRegisterLocation value) : count_(count), regs_small_{} {
     if (count_ <= kSmallCount) {
-      std::fill_n(regs_small_.begin(), count, DexRegisterLocation::None());
+      std::fill_n(regs_small_.begin(), count, value);
     } else {
-      regs_large_.resize(count, DexRegisterLocation::None());
+      regs_large_.resize(count, value);
     }
   }
 
@@ -62,9 +70,12 @@
     return count_ <= kSmallCount ? regs_small_.data() : regs_large_.data();
   }
 
+  iterator begin() { return data(); }
+  iterator end() { return data() + count_; }
+
   size_t size() const { return count_; }
 
-  bool IsValid() const { return count_ != 0; }
+  bool empty() const { return count_ == 0; }
 
   DexRegisterLocation Get(size_t index) const {
     DCHECK_LT(index, count_);
@@ -147,38 +158,26 @@
  */
 class StackMap : public BitTable<7>::Accessor {
  public:
-  enum Field {
-    kPackedNativePc,
-    kDexPc,
-    kRegisterMaskIndex,
-    kStackMaskIndex,
-    kInlineInfoIndex,
-    kDexRegisterMaskIndex,
-    kDexRegisterMapIndex,
-    kCount,
-  };
-
-  StackMap() : BitTable<kCount>::Accessor(nullptr, -1) {}
-  StackMap(const BitTable<kCount>* table, uint32_t row)
-    : BitTable<kCount>::Accessor(table, row) {}
+  BIT_TABLE_HEADER()
+  BIT_TABLE_COLUMN(0, PackedNativePc)
+  BIT_TABLE_COLUMN(1, DexPc)
+  BIT_TABLE_COLUMN(2, RegisterMaskIndex)
+  BIT_TABLE_COLUMN(3, StackMaskIndex)
+  BIT_TABLE_COLUMN(4, InlineInfoIndex)
+  BIT_TABLE_COLUMN(5, DexRegisterMaskIndex)
+  BIT_TABLE_COLUMN(6, DexRegisterMapIndex)
 
   ALWAYS_INLINE uint32_t GetNativePcOffset(InstructionSet instruction_set) const {
     return UnpackNativePc(Get<kPackedNativePc>(), instruction_set);
   }
 
-  uint32_t GetDexPc() const { return Get<kDexPc>(); }
+  ALWAYS_INLINE bool HasInlineInfo() const {
+    return HasInlineInfoIndex();
+  }
 
-  uint32_t GetDexRegisterMaskIndex() const { return Get<kDexRegisterMaskIndex>(); }
-
-  uint32_t GetDexRegisterMapIndex() const { return Get<kDexRegisterMapIndex>(); }
-  bool HasDexRegisterMap() const { return GetDexRegisterMapIndex() != kNoValue; }
-
-  uint32_t GetInlineInfoIndex() const { return Get<kInlineInfoIndex>(); }
-  bool HasInlineInfo() const { return GetInlineInfoIndex() != kNoValue; }
-
-  uint32_t GetRegisterMaskIndex() const { return Get<kRegisterMaskIndex>(); }
-
-  uint32_t GetStackMaskIndex() const { return Get<kStackMaskIndex>(); }
+  ALWAYS_INLINE bool HasDexRegisterMap() const {
+    return HasDexRegisterMapIndex();
+  }
 
   static uint32_t PackNativePc(uint32_t native_pc, InstructionSet isa) {
     DCHECK_ALIGNED_PARAM(native_pc, GetInstructionSetInstructionAlignment(isa));
@@ -195,7 +194,6 @@
             const CodeInfo& code_info,
             const MethodInfo& method_info,
             uint32_t code_offset,
-            uint16_t number_of_dex_registers,
             InstructionSet instruction_set) const;
 };
 
@@ -204,106 +202,65 @@
  * The row referenced from the StackMap holds information at depth 0.
  * Following rows hold information for further depths.
  */
-class InlineInfo : public BitTable<7>::Accessor {
+class InlineInfo : public BitTable<6>::Accessor {
  public:
-  enum Field {
-    kIsLast,  // Determines if there are further rows for further depths.
-    kDexPc,
-    kMethodIndexIdx,
-    kArtMethodHi,  // High bits of ArtMethod*.
-    kArtMethodLo,  // Low bits of ArtMethod*.
-    kDexRegisterMaskIndex,
-    kDexRegisterMapIndex,
-    kCount,
-  };
+  BIT_TABLE_HEADER()
+  BIT_TABLE_COLUMN(0, IsLast)  // Determines if there are further rows for further depths.
+  BIT_TABLE_COLUMN(1, DexPc)
+  BIT_TABLE_COLUMN(2, MethodInfoIndex)
+  BIT_TABLE_COLUMN(3, ArtMethodHi)  // High bits of ArtMethod*.
+  BIT_TABLE_COLUMN(4, ArtMethodLo)  // Low bits of ArtMethod*.
+  BIT_TABLE_COLUMN(5, NumberOfDexRegisters)  // Includes outer levels and the main method.
+  BIT_TABLE_COLUMN(6, DexRegisterMapIndex)
+
   static constexpr uint32_t kLast = -1;
   static constexpr uint32_t kMore = 0;
 
-  InlineInfo(const BitTable<kCount>* table, uint32_t row)
-    : BitTable<kCount>::Accessor(table, row) {}
-
-  uint32_t GetIsLast() const { return Get<kIsLast>(); }
-
-  uint32_t GetMethodIndexIdx() const {
-    DCHECK(!EncodesArtMethod());
-    return Get<kMethodIndexIdx>();
-  }
-
   uint32_t GetMethodIndex(const MethodInfo& method_info) const {
-    return method_info.GetMethodIndex(GetMethodIndexIdx());
-  }
-
-  uint32_t GetDexPc() const {
-    return Get<kDexPc>();
+    return method_info.GetMethodIndex(GetMethodInfoIndex());
   }
 
   bool EncodesArtMethod() const {
-    return Get<kArtMethodLo>() != kNoValue;
+    return HasArtMethodLo();
   }
 
   ArtMethod* GetArtMethod() const {
-    uint64_t lo = Get<kArtMethodLo>();
-    uint64_t hi = Get<kArtMethodHi>();
+    uint64_t lo = GetArtMethodLo();
+    uint64_t hi = GetArtMethodHi();
     return reinterpret_cast<ArtMethod*>((hi << 32) | lo);
   }
 
-  uint32_t GetDexRegisterMaskIndex() const {
-    return Get<kDexRegisterMaskIndex>();
-  }
-
-  uint32_t GetDexRegisterMapIndex() const {
-    return Get<kDexRegisterMapIndex>();
-  }
-  bool HasDexRegisterMap() const {
-    return GetDexRegisterMapIndex() != kNoValue;
-  }
-
   void Dump(VariableIndentationOutputStream* vios,
             const CodeInfo& info,
             const StackMap& stack_map,
-            const MethodInfo& method_info,
-            uint16_t number_of_dex_registers) const;
+            const MethodInfo& method_info) const;
 };
 
 class InvokeInfo : public BitTable<3>::Accessor {
  public:
-  enum Field {
-    kPackedNativePc,
-    kInvokeType,
-    kMethodIndexIdx,
-    kCount,
-  };
-
-  InvokeInfo(const BitTable<kCount>* table, uint32_t row)
-    : BitTable<kCount>::Accessor(table, row) {}
+  BIT_TABLE_HEADER()
+  BIT_TABLE_COLUMN(0, PackedNativePc)
+  BIT_TABLE_COLUMN(1, InvokeType)
+  BIT_TABLE_COLUMN(2, MethodInfoIndex)
 
   ALWAYS_INLINE uint32_t GetNativePcOffset(InstructionSet instruction_set) const {
     return StackMap::UnpackNativePc(Get<kPackedNativePc>(), instruction_set);
   }
 
-  uint32_t GetInvokeType() const { return Get<kInvokeType>(); }
-
-  uint32_t GetMethodIndexIdx() const { return Get<kMethodIndexIdx>(); }
-
   uint32_t GetMethodIndex(MethodInfo method_info) const {
-    return method_info.GetMethodIndex(GetMethodIndexIdx());
+    return method_info.GetMethodIndex(GetMethodInfoIndex());
   }
 };
 
 class DexRegisterInfo : public BitTable<2>::Accessor {
  public:
-  enum Field {
-    kKind,
-    kPackedValue,
-    kCount,
-  };
-
-  DexRegisterInfo(const BitTable<kCount>* table, uint32_t row)
-    : BitTable<kCount>::Accessor(table, row) {}
+  BIT_TABLE_HEADER()
+  BIT_TABLE_COLUMN(0, Kind)
+  BIT_TABLE_COLUMN(1, PackedValue)
 
   ALWAYS_INLINE DexRegisterLocation GetLocation() const {
-    DexRegisterLocation::Kind kind = static_cast<DexRegisterLocation::Kind>(Get<kKind>());
-    return DexRegisterLocation(kind, UnpackValue(kind, Get<kPackedValue>()));
+    DexRegisterLocation::Kind kind = static_cast<DexRegisterLocation::Kind>(GetKind());
+    return DexRegisterLocation(kind, UnpackValue(kind, GetPackedValue()));
   }
 
   static uint32_t PackValue(DexRegisterLocation::Kind kind, uint32_t value) {
@@ -328,17 +285,12 @@
 // therefore it is worth encoding the mask as value+shift.
 class RegisterMask : public BitTable<2>::Accessor {
  public:
-  enum Field {
-    kValue,
-    kShift,
-    kCount,
-  };
-
-  RegisterMask(const BitTable<kCount>* table, uint32_t row)
-    : BitTable<kCount>::Accessor(table, row) {}
+  BIT_TABLE_HEADER()
+  BIT_TABLE_COLUMN(0, Value)
+  BIT_TABLE_COLUMN(1, Shift)
 
   ALWAYS_INLINE uint32_t GetMask() const {
-    return Get<kValue>() << Get<kShift>();
+    return GetValue() << GetShift();
   }
 };
 
@@ -391,7 +343,9 @@
   }
 
   ALWAYS_INLINE DexRegisterLocation GetDexRegisterCatalogEntry(size_t index) const {
-    return DexRegisterInfo(&dex_register_catalog_, index).GetLocation();
+    return (index == StackMap::kNoValue)
+      ? DexRegisterLocation::None()
+      : DexRegisterInfo(&dex_register_catalog_, index).GetLocation();
   }
 
   uint32_t GetNumberOfStackMaps() const {
@@ -402,20 +356,28 @@
     return InvokeInfo(&invoke_infos_, index);
   }
 
-  ALWAYS_INLINE DexRegisterMap GetDexRegisterMapOf(StackMap stack_map,
-                                                   size_t num_dex_registers) const {
-    return DecodeDexRegisterMap(stack_map.GetDexRegisterMaskIndex(),
-                                stack_map.GetDexRegisterMapIndex(),
-                                num_dex_registers);
+  ALWAYS_INLINE DexRegisterMap GetDexRegisterMapOf(StackMap stack_map) const {
+    if (stack_map.HasDexRegisterMap()) {
+      DexRegisterMap map(number_of_dex_registers_, DexRegisterLocation::Invalid());
+      DecodeDexRegisterMap(stack_map.Row(), /* first_dex_register */ 0, &map);
+      return map;
+    }
+    return DexRegisterMap(0, DexRegisterLocation::None());
   }
 
-  ALWAYS_INLINE DexRegisterMap GetDexRegisterMapAtDepth(uint8_t depth,
-                                                        StackMap stack_map,
-                                                        size_t num_dex_registers) const {
-    InlineInfo inline_info = GetInlineInfoAtDepth(stack_map, depth);
-    return DecodeDexRegisterMap(inline_info.GetDexRegisterMaskIndex(),
-                                inline_info.GetDexRegisterMapIndex(),
-                                num_dex_registers);
+  ALWAYS_INLINE DexRegisterMap GetDexRegisterMapAtDepth(uint8_t depth, StackMap stack_map) const {
+    if (stack_map.HasDexRegisterMap()) {
+      // The register counts are commutative and include all outer levels.
+      // This allows us to determine the range [first, last) in just two lookups.
+      // If we are at depth 0 (the first inlinee), the count from the main method is used.
+      uint32_t first = (depth == 0) ? number_of_dex_registers_
+          : GetInlineInfoAtDepth(stack_map, depth - 1).GetNumberOfDexRegisters();
+      uint32_t last = GetInlineInfoAtDepth(stack_map, depth).GetNumberOfDexRegisters();
+      DexRegisterMap map(last - first, DexRegisterLocation::Invalid());
+      DecodeDexRegisterMap(stack_map.Row(), first, &map);
+      return map;
+    }
+    return DexRegisterMap(0, DexRegisterLocation::None());
   }
 
   InlineInfo GetInlineInfo(size_t index) const {
@@ -474,8 +436,6 @@
         if (other.GetDexPc() == dex_pc &&
             other.GetNativePcOffset(kRuntimeISA) ==
                 stack_map.GetNativePcOffset(kRuntimeISA)) {
-          DCHECK_EQ(other.GetDexRegisterMapIndex(),
-                    stack_map.GetDexRegisterMapIndex());
           if (i < e - 2) {
             // Make sure there are not three identical stack maps following each other.
             DCHECK_NE(
@@ -509,36 +469,22 @@
         return item;
       }
     }
-    return InvokeInfo(&invoke_infos_, -1);
+    return InvokeInfo();
   }
 
   // Dump this CodeInfo object on `vios`.
   // `code_offset` is the (absolute) native PC of the compiled method.
   void Dump(VariableIndentationOutputStream* vios,
             uint32_t code_offset,
-            uint16_t number_of_dex_registers,
             bool verbose,
             InstructionSet instruction_set,
             const MethodInfo& method_info) const;
 
  private:
-  ALWAYS_INLINE DexRegisterMap DecodeDexRegisterMap(uint32_t mask_index,
-                                                    uint32_t map_index,
-                                                    uint32_t num_dex_registers) const {
-    DexRegisterMap map(map_index == StackMap::kNoValue ? 0 : num_dex_registers);
-    if (mask_index != StackMap::kNoValue) {
-      BitMemoryRegion mask = dex_register_masks_.GetBitMemoryRegion(mask_index);
-      num_dex_registers = std::min<uint32_t>(num_dex_registers, mask.size_in_bits());
-      DexRegisterLocation* regs = map.data();
-      for (uint32_t r = 0; r < mask.size_in_bits(); r++) {
-        if (mask.LoadBit(r) /* is_live */) {
-          DCHECK_LT(r, map.size());
-          regs[r] = GetDexRegisterCatalogEntry(dex_register_maps_.Get(map_index++));
-        }
-      }
-    }
-    return map;
-  }
+  // Scan backward to determine dex register locations at given stack map.
+  void DecodeDexRegisterMap(uint32_t stack_map_index,
+                            uint32_t first_dex_register,
+                            /*out*/ DexRegisterMap* map) const;
 
   void Decode(const uint8_t* data) {
     size_t non_header_size = DecodeUnsignedLeb128(&data);
@@ -553,18 +499,20 @@
     dex_register_masks_.Decode(region, &bit_offset);
     dex_register_maps_.Decode(region, &bit_offset);
     dex_register_catalog_.Decode(region, &bit_offset);
+    number_of_dex_registers_ = DecodeVarintBits(region, &bit_offset);
     CHECK_EQ(non_header_size, BitsToBytesRoundUp(bit_offset)) << "Invalid CodeInfo";
   }
 
   size_t size_;
-  BitTable<StackMap::Field::kCount> stack_maps_;
-  BitTable<RegisterMask::Field::kCount> register_masks_;
+  BitTable<StackMap::kCount> stack_maps_;
+  BitTable<RegisterMask::kCount> register_masks_;
   BitTable<1> stack_masks_;
-  BitTable<InvokeInfo::Field::kCount> invoke_infos_;
-  BitTable<InlineInfo::Field::kCount> inline_infos_;
+  BitTable<InvokeInfo::kCount> invoke_infos_;
+  BitTable<InlineInfo::kCount> inline_infos_;
   BitTable<1> dex_register_masks_;
   BitTable<1> dex_register_maps_;
-  BitTable<DexRegisterInfo::Field::kCount> dex_register_catalog_;
+  BitTable<DexRegisterInfo::kCount> dex_register_catalog_;
+  uint32_t number_of_dex_registers_;  // Excludes any inlined methods.
 
   friend class OatDumper;
 };
diff --git a/runtime/thread.cc b/runtime/thread.cc
index 210e1b0..4a53425 100644
--- a/runtime/thread.cc
+++ b/runtime/thread.cc
@@ -3645,8 +3645,7 @@
                        RootVisitor& _visitor)
           : number_of_dex_registers(method->DexInstructionData().RegistersSize()),
             code_info(_code_info),
-            dex_register_map(code_info.GetDexRegisterMapOf(map,
-                                                           number_of_dex_registers)),
+            dex_register_map(code_info.GetDexRegisterMapOf(map)),
             visitor(_visitor) {
       }
 
diff --git a/runtime/verifier/method_verifier.cc b/runtime/verifier/method_verifier.cc
index 5961748..61ddded 100644
--- a/runtime/verifier/method_verifier.cc
+++ b/runtime/verifier/method_verifier.cc
@@ -212,8 +212,6 @@
                                         bool allow_soft_failures,
                                         HardFailLogMode log_level,
                                         std::string* error) {
-  SCOPED_TRACE << "VerifyClass " << PrettyDescriptor(dex_file->GetClassDescriptor(class_def));
-
   // A class must not be abstract and final.
   if ((class_def.access_flags_ & (kAccAbstract | kAccFinal)) == (kAccAbstract | kAccFinal)) {
     *error = "Verifier rejected class ";
@@ -223,6 +221,7 @@
   }
 
   ClassAccessor accessor(*dex_file, class_def);
+  SCOPED_TRACE << "VerifyClass " << PrettyDescriptor(accessor.GetDescriptor());
 
   int64_t previous_method_idx[2] = { -1, -1 };
   MethodVerifier::FailureData failure_data;
@@ -3054,10 +3053,7 @@
       // Step 2. Check the register arguments correspond to the expected arguments for the
       // method handle produced by step 1. The dex file verifier has checked ranges for
       // the first three arguments and CheckCallSite has checked the method handle type.
-      CallSiteArrayValueIterator it(*dex_file_, dex_file_->GetCallSiteId(call_site_idx));
-      it.Next();  // Skip to name.
-      it.Next();  // Skip to method type of the method handle
-      const dex::ProtoIndex proto_idx(it.GetJavaValue().c);
+      const dex::ProtoIndex proto_idx = dex_file_->GetProtoIndexForCallSite(call_site_idx);
       const DexFile::ProtoId& proto_id = dex_file_->GetProtoId(proto_idx);
       DexFileParameterIterator param_it(*dex_file_, proto_id);
       // Treat method as static as it has yet to be determined.
@@ -3073,8 +3069,6 @@
         work_line_->SetResultRegisterTypeWide(return_type, return_type.HighHalf(&reg_types_));
       }
       just_set_result = true;
-      // TODO: Add compiler support for invoke-custom (b/35337872).
-      Fail(VERIFY_ERROR_FORCE_INTERPRETER);
       break;
     }
     case Instruction::NEG_INT:
@@ -4008,24 +4002,41 @@
   CallSiteArrayValueIterator it(*dex_file_, dex_file_->GetCallSiteId(call_site_idx));
   // Check essential arguments are provided. The dex file verifier has verified indicies of the
   // main values (method handle, name, method_type).
-  if (it.Size() < 3) {
+  static const size_t kRequiredArguments = 3;
+  if (it.Size() < kRequiredArguments) {
     Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "Call site #" << call_site_idx
                                       << " has too few arguments: "
-                                      << it.Size() << " < 3";
+                                      << it.Size() << " < " << kRequiredArguments;
     return false;
   }
 
-  // Get and check the first argument: the method handle (index range
-  // checked by the dex file verifier).
-  uint32_t method_handle_idx = static_cast<uint32_t>(it.GetJavaValue().i);
-  if (method_handle_idx > dex_file_->NumMethodHandles()) {
-    Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "Call site id #" << call_site_idx
-                                      << " method handle index invalid " << method_handle_idx
-                                      << " >= "  << dex_file_->NumMethodHandles();
-    return false;
+  std::pair<const EncodedArrayValueIterator::ValueType, size_t> type_and_max[kRequiredArguments] =
+      { { EncodedArrayValueIterator::ValueType::kMethodHandle, dex_file_->NumMethodHandles() },
+        { EncodedArrayValueIterator::ValueType::kString, dex_file_->NumStringIds() },
+        { EncodedArrayValueIterator::ValueType::kMethodType, dex_file_->NumProtoIds() }
+      };
+  uint32_t index[kRequiredArguments];
+
+  // Check arguments have expected types and are within permitted ranges.
+  for (size_t i = 0; i < kRequiredArguments; ++i) {
+    if (it.GetValueType() != type_and_max[i].first) {
+      Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "Call site id #" << call_site_idx
+                                        << " argument " << i << " has wrong type "
+                                        << it.GetValueType() << "!=" << type_and_max[i].first;
+      return false;
+    }
+    index[i] = static_cast<uint32_t>(it.GetJavaValue().i);
+    if (index[i] >= type_and_max[i].second) {
+      Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "Call site id #" << call_site_idx
+                                        << " argument " << i << " bad index "
+                                        << index[i] << " >= " << type_and_max[i].second;
+      return false;
+    }
+    it.Next();
   }
 
-  const DexFile::MethodHandleItem& mh = dex_file_->GetMethodHandle(method_handle_idx);
+  // Check method handle kind is valid.
+  const DexFile::MethodHandleItem& mh = dex_file_->GetMethodHandle(index[0]);
   if (mh.method_handle_type_ != static_cast<uint16_t>(DexFile::MethodHandleType::kInvokeStatic)) {
     Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "Call site #" << call_site_idx
                                       << " argument 0 method handle type is not InvokeStatic: "
diff --git a/test/551-checker-shifter-operand/build b/test/551-checker-shifter-operand/build
deleted file mode 100644
index d85147f..0000000
--- a/test/551-checker-shifter-operand/build
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2018 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.
-
-# See b/65168732
-export USE_D8=false
-
-./default-build "$@"
diff --git a/test/551-checker-shifter-operand/src/Main.java b/test/551-checker-shifter-operand/src/Main.java
index b3e4a60..8311b60 100644
--- a/test/551-checker-shifter-operand/src/Main.java
+++ b/test/551-checker-shifter-operand/src/Main.java
@@ -896,7 +896,7 @@
   }
 
   // Each test line below should see one merge.
-  /// CHECK-START-ARM: void Main.$opt$validateShiftLong(long, long) instruction_simplifier_arm (after)
+  /// CHECK-START-ARM: long[] Main.$opt$validateShiftLong(long, long) instruction_simplifier_arm (after)
   /// CHECK:                            DataProcWithShifterOp
   /// CHECK:                            DataProcWithShifterOp
   /// CHECK:                            DataProcWithShifterOp
@@ -933,7 +933,7 @@
   /// CHECK-NOT:                        DataProcWithShifterOp
 
   // On ARM shifts by 1 are not merged.
-  /// CHECK-START-ARM: void Main.$opt$validateShiftLong(long, long) instruction_simplifier_arm (after)
+  /// CHECK-START-ARM: long[] Main.$opt$validateShiftLong(long, long) instruction_simplifier_arm (after)
   /// CHECK:                            Shl
   /// CHECK-NOT:                        Shl
   /// CHECK:                            Shr
@@ -941,7 +941,7 @@
   /// CHECK:                            UShr
   /// CHECK-NOT:                        UShr
 
-  /// CHECK-START-ARM64: void Main.$opt$validateShiftLong(long, long) instruction_simplifier_arm64 (after)
+  /// CHECK-START-ARM64: long[] Main.$opt$validateShiftLong(long, long) instruction_simplifier_arm64 (after)
   /// CHECK:                            DataProcWithShifterOp
   /// CHECK:                            DataProcWithShifterOp
   /// CHECK:                            DataProcWithShifterOp
@@ -980,50 +980,98 @@
   /// CHECK:                            DataProcWithShifterOp
   /// CHECK-NOT:                        DataProcWithShifterOp
 
-  /// CHECK-START-ARM64: void Main.$opt$validateShiftLong(long, long) instruction_simplifier_arm64 (after)
+  /// CHECK-START-ARM64: long[] Main.$opt$validateShiftLong(long, long) instruction_simplifier_arm64 (after)
   /// CHECK-NOT:                        Shl
   /// CHECK-NOT:                        Shr
   /// CHECK-NOT:                        UShr
 
-  public static void $opt$validateShiftLong(long a, long b) {
-    assertLongEquals(a + $noinline$LongShl(b, 1),   a + (b <<  1));
-    assertLongEquals(a + $noinline$LongShl(b, 6),   a + (b <<  6));
-    assertLongEquals(a + $noinline$LongShl(b, 7),   a + (b <<  7));
-    assertLongEquals(a + $noinline$LongShl(b, 8),   a + (b <<  8));
-    assertLongEquals(a + $noinline$LongShl(b, 14),  a + (b << 14));
-    assertLongEquals(a + $noinline$LongShl(b, 15),  a + (b << 15));
-    assertLongEquals(a + $noinline$LongShl(b, 16),  a + (b << 16));
-    assertLongEquals(a + $noinline$LongShl(b, 30),  a + (b << 30));
-    assertLongEquals(a + $noinline$LongShl(b, 31),  a + (b << 31));
-    assertLongEquals(a + $noinline$LongShl(b, 32),  a + (b << 32));
-    assertLongEquals(a + $noinline$LongShl(b, 62),  a + (b << 62));
-    assertLongEquals(a + $noinline$LongShl(b, 63),  a + (b << 63));
+  public static long[] $opt$validateShiftLong(long a, long b) {
+    long[] results = new long[36];
 
-    assertLongEquals(a - $noinline$LongShr(b, 1),   a - (b >>  1));
-    assertLongEquals(a - $noinline$LongShr(b, 6),   a - (b >>  6));
-    assertLongEquals(a - $noinline$LongShr(b, 7),   a - (b >>  7));
-    assertLongEquals(a - $noinline$LongShr(b, 8),   a - (b >>  8));
-    assertLongEquals(a - $noinline$LongShr(b, 14),  a - (b >> 14));
-    assertLongEquals(a - $noinline$LongShr(b, 15),  a - (b >> 15));
-    assertLongEquals(a - $noinline$LongShr(b, 16),  a - (b >> 16));
-    assertLongEquals(a - $noinline$LongShr(b, 30),  a - (b >> 30));
-    assertLongEquals(a - $noinline$LongShr(b, 31),  a - (b >> 31));
-    assertLongEquals(a - $noinline$LongShr(b, 32),  a - (b >> 32));
-    assertLongEquals(a - $noinline$LongShr(b, 62),  a - (b >> 62));
-    assertLongEquals(a - $noinline$LongShr(b, 63),  a - (b >> 63));
+    results[0] = a + (b <<  1);
+    results[1] = a + (b <<  6);
+    results[2] = a + (b <<  7);
+    results[3] = a + (b <<  8);
+    results[4] = a + (b << 14);
+    results[5] = a + (b << 15);
+    results[6] = a + (b << 16);
+    results[7] = a + (b << 30);
+    results[8] = a + (b << 31);
+    results[9] = a + (b << 32);
+    results[10] = a + (b << 62);
+    results[11] = a + (b << 63);
 
-    assertLongEquals(a ^ $noinline$LongUshr(b, 1),   a ^ (b >>>  1));
-    assertLongEquals(a ^ $noinline$LongUshr(b, 6),   a ^ (b >>>  6));
-    assertLongEquals(a ^ $noinline$LongUshr(b, 7),   a ^ (b >>>  7));
-    assertLongEquals(a ^ $noinline$LongUshr(b, 8),   a ^ (b >>>  8));
-    assertLongEquals(a ^ $noinline$LongUshr(b, 14),  a ^ (b >>> 14));
-    assertLongEquals(a ^ $noinline$LongUshr(b, 15),  a ^ (b >>> 15));
-    assertLongEquals(a ^ $noinline$LongUshr(b, 16),  a ^ (b >>> 16));
-    assertLongEquals(a ^ $noinline$LongUshr(b, 30),  a ^ (b >>> 30));
-    assertLongEquals(a ^ $noinline$LongUshr(b, 31),  a ^ (b >>> 31));
-    assertLongEquals(a ^ $noinline$LongUshr(b, 32),  a ^ (b >>> 32));
-    assertLongEquals(a ^ $noinline$LongUshr(b, 62),  a ^ (b >>> 62));
-    assertLongEquals(a ^ $noinline$LongUshr(b, 63),  a ^ (b >>> 63));
+    results[12] = a - (b >>  1);
+    results[13] = a - (b >>  6);
+    results[14] = a - (b >>  7);
+    results[15] = a - (b >>  8);
+    results[16] = a - (b >> 14);
+    results[17] = a - (b >> 15);
+    results[18] = a - (b >> 16);
+    results[19] = a - (b >> 30);
+    results[20] = a - (b >> 31);
+    results[21] = a - (b >> 32);
+    results[22] = a - (b >> 62);
+    results[23] = a - (b >> 63);
+
+    results[24] = a ^ (b >>>  1);
+    results[25] = a ^ (b >>>  6);
+    results[26] = a ^ (b >>>  7);
+    results[27] = a ^ (b >>>  8);
+    results[28] = a ^ (b >>> 14);
+    results[29] = a ^ (b >>> 15);
+    results[30] = a ^ (b >>> 16);
+    results[31] = a ^ (b >>> 30);
+    results[32] = a ^ (b >>> 31);
+    results[33] = a ^ (b >>> 32);
+    results[34] = a ^ (b >>> 62);
+    results[35] = a ^ (b >>> 63);
+
+    return results;
+  }
+
+  public static void $opt$validateShiftLongAsserts(long a, long b) {
+    long[] results = $opt$validateShiftLong(a, b);
+    assertIntEquals(3 * 12, results.length);
+
+    assertLongEquals(a + $noinline$LongShl(b, 1),  results[0]);
+    assertLongEquals(a + $noinline$LongShl(b, 6),  results[1]);
+    assertLongEquals(a + $noinline$LongShl(b, 7),  results[2]);
+    assertLongEquals(a + $noinline$LongShl(b, 8),  results[3]);
+    assertLongEquals(a + $noinline$LongShl(b, 14), results[4]);
+    assertLongEquals(a + $noinline$LongShl(b, 15), results[5]);
+    assertLongEquals(a + $noinline$LongShl(b, 16), results[6]);
+    assertLongEquals(a + $noinline$LongShl(b, 30), results[7]);
+    assertLongEquals(a + $noinline$LongShl(b, 31), results[8]);
+    assertLongEquals(a + $noinline$LongShl(b, 32), results[9]);
+    assertLongEquals(a + $noinline$LongShl(b, 62), results[10]);
+    assertLongEquals(a + $noinline$LongShl(b, 63), results[11]);
+
+    assertLongEquals(a - $noinline$LongShr(b, 1),  results[12]);
+    assertLongEquals(a - $noinline$LongShr(b, 6),  results[13]);
+    assertLongEquals(a - $noinline$LongShr(b, 7),  results[14]);
+    assertLongEquals(a - $noinline$LongShr(b, 8),  results[15]);
+    assertLongEquals(a - $noinline$LongShr(b, 14), results[16]);
+    assertLongEquals(a - $noinline$LongShr(b, 15), results[17]);
+    assertLongEquals(a - $noinline$LongShr(b, 16), results[18]);
+    assertLongEquals(a - $noinline$LongShr(b, 30), results[19]);
+    assertLongEquals(a - $noinline$LongShr(b, 31), results[20]);
+    assertLongEquals(a - $noinline$LongShr(b, 32), results[21]);
+    assertLongEquals(a - $noinline$LongShr(b, 62), results[22]);
+    assertLongEquals(a - $noinline$LongShr(b, 63), results[23]);
+
+    assertLongEquals(a ^ $noinline$LongUshr(b, 1),  results[24]);
+    assertLongEquals(a ^ $noinline$LongUshr(b, 6),  results[25]);
+    assertLongEquals(a ^ $noinline$LongUshr(b, 7),  results[26]);
+    assertLongEquals(a ^ $noinline$LongUshr(b, 8),  results[27]);
+    assertLongEquals(a ^ $noinline$LongUshr(b, 14), results[28]);
+    assertLongEquals(a ^ $noinline$LongUshr(b, 15), results[29]);
+    assertLongEquals(a ^ $noinline$LongUshr(b, 16), results[30]);
+    assertLongEquals(a ^ $noinline$LongUshr(b, 30), results[31]);
+    assertLongEquals(a ^ $noinline$LongUshr(b, 31), results[32]);
+    assertLongEquals(a ^ $noinline$LongUshr(b, 32), results[33]);
+    assertLongEquals(a ^ $noinline$LongUshr(b, 62), results[34]);
+    assertLongEquals(a ^ $noinline$LongUshr(b, 63), results[35]);
   }
 
 
@@ -1072,7 +1120,7 @@
         $opt$validateExtendLong(inputs[i], inputs[j]);
 
         $opt$validateShiftInt((int)inputs[i], (int)inputs[j]);
-        $opt$validateShiftLong(inputs[i], inputs[j]);
+        $opt$validateShiftLongAsserts(inputs[i], inputs[j]);
       }
     }
 
diff --git a/test/565-checker-doublenegbitwise/build b/test/565-checker-doublenegbitwise/build
deleted file mode 100755
index 10ffcc5..0000000
--- a/test/565-checker-doublenegbitwise/build
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2017 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-# See b/65168732
-export USE_D8=false
-
-./default-build "$@"
diff --git a/test/565-checker-doublenegbitwise/smali/SmaliTests.smali b/test/565-checker-doublenegbitwise/smali/SmaliTests.smali
index 2e08022..ce69154 100644
--- a/test/565-checker-doublenegbitwise/smali/SmaliTests.smali
+++ b/test/565-checker-doublenegbitwise/smali/SmaliTests.smali
@@ -403,3 +403,591 @@
     sput-boolean v0, LSmaliTests;->doThrow:Z
     return-void
 .end method
+
+
+# Test transformation of Not/Not/And into Or/Not.
+
+# Note: before the instruction_simplifier pass, Xor's are used instead of
+# Not's (the simplification happens during the same pass).
+## CHECK-START: int SmaliTests.$opt$noinline$andToOrV2(int, int) instruction_simplifier (before)
+## CHECK-DAG:       <<P1:i\d+>>          ParameterValue
+## CHECK-DAG:       <<P2:i\d+>>          ParameterValue
+## CHECK-DAG:       <<CstM1:i\d+>>       IntConstant -1
+## CHECK-DAG:       <<Not1:i\d+>>        Xor [<<P1>>,<<CstM1>>]
+## CHECK-DAG:       <<Not2:i\d+>>        Xor [<<P2>>,<<CstM1>>]
+## CHECK-DAG:       <<And:i\d+>>         And [<<Not1>>,<<Not2>>]
+## CHECK-DAG:                            Return [<<And>>]
+
+## CHECK-START: int SmaliTests.$opt$noinline$andToOrV2(int, int) instruction_simplifier (after)
+## CHECK-DAG:       <<P1:i\d+>>          ParameterValue
+## CHECK-DAG:       <<P2:i\d+>>          ParameterValue
+## CHECK-DAG:       <<Or:i\d+>>          Or [<<P1>>,<<P2>>]
+## CHECK-DAG:       <<Not:i\d+>>         Not [<<Or>>]
+## CHECK-DAG:                            Return [<<Not>>]
+
+## CHECK-START: int SmaliTests.$opt$noinline$andToOrV2(int, int) instruction_simplifier (after)
+## CHECK-DAG:                            Not
+## CHECK-NOT:                            Not
+
+## CHECK-START: int SmaliTests.$opt$noinline$andToOrV2(int, int) instruction_simplifier (after)
+## CHECK-NOT:                            And
+
+# Original java source:
+#
+#     public static int $opt$noinline$andToOr(int a, int b) {
+#       if (doThrow) throw new Error();
+#       return ~a & ~b;
+#     }
+
+.method public static $opt$noinline$andToOrV2(II)I
+    .registers 4
+    .param p0, "a"    # I
+    .param p1, "b"    # I
+
+    .prologue
+    .line 85
+    sget-boolean v0, LMain;->doThrow:Z
+
+    if-eqz v0, :cond_a
+
+    new-instance v0, Ljava/lang/Error;
+
+    invoke-direct {v0}, Ljava/lang/Error;-><init>()V
+
+    throw v0
+
+    .line 86
+    :cond_a
+    xor-int/lit8 v0, p0, -0x1
+
+    xor-int/lit8 v1, p1, -0x1
+
+    and-int/2addr v0, v1
+
+    return v0
+.end method
+
+
+# Test transformation of Not/Not/And into Or/Not for boolean negations.
+# Note that the graph before this instruction simplification pass does not
+# contain `HBooleanNot` instructions. This is because this transformation
+# follows the optimization of `HSelect` to `HBooleanNot` occurring in the
+# same pass.
+
+## CHECK-START: boolean SmaliTests.$opt$noinline$booleanAndToOrV2(boolean, boolean) instruction_simplifier$after_gvn (before)
+## CHECK-DAG:       <<P1:z\d+>>          ParameterValue
+## CHECK-DAG:       <<P2:z\d+>>          ParameterValue
+## CHECK-DAG:       <<Const0:i\d+>>      IntConstant 0
+## CHECK-DAG:       <<Const1:i\d+>>      IntConstant 1
+## CHECK-DAG:       <<Select1:i\d+>>     Select [<<Const1>>,<<Const0>>,<<P1>>]
+## CHECK-DAG:       <<Select2:i\d+>>     Select [<<Const1>>,<<Const0>>,<<P2>>]
+## CHECK-DAG:       <<And:i\d+>>         And [<<Select1>>,<<Select2>>]
+## CHECK-DAG:                            Return [<<And>>]
+
+## CHECK-START: boolean SmaliTests.$opt$noinline$booleanAndToOrV2(boolean, boolean) instruction_simplifier$after_gvn (after)
+## CHECK-DAG:       <<Cond1:z\d+>>       ParameterValue
+## CHECK-DAG:       <<Cond2:z\d+>>       ParameterValue
+## CHECK-DAG:       <<Or:i\d+>>          Or [<<Cond1>>,<<Cond2>>]
+## CHECK-DAG:       <<BooleanNot:z\d+>>  BooleanNot [<<Or>>]
+## CHECK-DAG:                            Return [<<BooleanNot>>]
+
+## CHECK-START: boolean SmaliTests.$opt$noinline$booleanAndToOrV2(boolean, boolean) instruction_simplifier$after_bce (after)
+## CHECK-DAG:                            BooleanNot
+## CHECK-NOT:                            BooleanNot
+
+## CHECK-START: boolean SmaliTests.$opt$noinline$booleanAndToOrV2(boolean, boolean) instruction_simplifier$after_bce (after)
+## CHECK-NOT:                            And
+
+# Original java source:
+#
+#     public static boolean $opt$noinline$booleanAndToOr(boolean a, boolean b) {
+#       if (doThrow) throw new Error();
+#       return !a & !b;
+#     }
+
+.method public static $opt$noinline$booleanAndToOrV2(ZZ)Z
+    .registers 5
+    .param p0, "a"    # Z
+    .param p1, "b"    # Z
+
+    .prologue
+    const/4 v0, 0x1
+
+    const/4 v1, 0x0
+
+    .line 122
+    sget-boolean v2, LMain;->doThrow:Z
+
+    if-eqz v2, :cond_c
+
+    new-instance v0, Ljava/lang/Error;
+
+    invoke-direct {v0}, Ljava/lang/Error;-><init>()V
+
+    throw v0
+
+    .line 123
+    :cond_c
+    if-nez p0, :cond_13
+
+    move v2, v0
+
+    :goto_f
+    if-nez p1, :cond_15
+
+    :goto_11
+    and-int/2addr v0, v2
+
+    return v0
+
+    :cond_13
+    move v2, v1
+
+    goto :goto_f
+
+    :cond_15
+    move v0, v1
+
+    goto :goto_11
+.end method
+
+
+# Test transformation of Not/Not/Or into And/Not.
+
+# See note above.
+# The second Xor has its arguments reversed for no obvious reason.
+## CHECK-START: long SmaliTests.$opt$noinline$orToAndV2(long, long) instruction_simplifier (before)
+## CHECK-DAG:       <<P1:j\d+>>          ParameterValue
+## CHECK-DAG:       <<P2:j\d+>>          ParameterValue
+## CHECK-DAG:       <<CstM1:j\d+>>       LongConstant -1
+## CHECK-DAG:       <<Not1:j\d+>>        Xor [<<P1>>,<<CstM1>>]
+## CHECK-DAG:       <<Not2:j\d+>>        Xor [<<CstM1>>,<<P2>>]
+## CHECK-DAG:       <<Or:j\d+>>          Or [<<Not1>>,<<Not2>>]
+## CHECK-DAG:                            Return [<<Or>>]
+
+## CHECK-START: long SmaliTests.$opt$noinline$orToAndV2(long, long) instruction_simplifier (after)
+## CHECK-DAG:       <<P1:j\d+>>          ParameterValue
+## CHECK-DAG:       <<P2:j\d+>>          ParameterValue
+## CHECK-DAG:       <<And:j\d+>>         And [<<P1>>,<<P2>>]
+## CHECK-DAG:       <<Not:j\d+>>         Not [<<And>>]
+## CHECK-DAG:                            Return [<<Not>>]
+
+## CHECK-START: long SmaliTests.$opt$noinline$orToAndV2(long, long) instruction_simplifier (after)
+## CHECK-DAG:                            Not
+## CHECK-NOT:                            Not
+
+## CHECK-START: long SmaliTests.$opt$noinline$orToAndV2(long, long) instruction_simplifier (after)
+## CHECK-NOT:                            Or
+
+# Original java source:
+#
+#     public static long $opt$noinline$orToAnd(long a, long b) {
+#       if (doThrow) throw new Error();
+#       return ~a | ~b;
+#     }
+
+.method public static $opt$noinline$orToAndV2(JJ)J
+    .registers 8
+    .param p0, "a"    # J
+    .param p2, "b"    # J
+
+    .prologue
+    const-wide/16 v2, -0x1
+
+    .line 156
+    sget-boolean v0, LMain;->doThrow:Z
+
+    if-eqz v0, :cond_c
+
+    new-instance v0, Ljava/lang/Error;
+
+    invoke-direct {v0}, Ljava/lang/Error;-><init>()V
+
+    throw v0
+
+    .line 157
+    :cond_c
+    xor-long v0, p0, v2
+
+    xor-long/2addr v2, p2
+
+    or-long/2addr v0, v2
+
+    return-wide v0
+.end method
+
+# Test transformation of Not/Not/Or into Or/And for boolean negations.
+# Note that the graph before this instruction simplification pass does not
+# contain `HBooleanNot` instructions. This is because this transformation
+# follows the optimization of `HSelect` to `HBooleanNot` occurring in the
+# same pass.
+
+## CHECK-START: boolean SmaliTests.$opt$noinline$booleanOrToAndV2(boolean, boolean) instruction_simplifier$after_gvn (before)
+## CHECK-DAG:       <<P1:z\d+>>          ParameterValue
+## CHECK-DAG:       <<P2:z\d+>>          ParameterValue
+## CHECK-DAG:       <<Const0:i\d+>>      IntConstant 0
+## CHECK-DAG:       <<Const1:i\d+>>      IntConstant 1
+## CHECK-DAG:       <<Select1:i\d+>>     Select [<<Const1>>,<<Const0>>,<<P1>>]
+## CHECK-DAG:       <<Select2:i\d+>>     Select [<<Const1>>,<<Const0>>,<<P2>>]
+## CHECK-DAG:       <<Or:i\d+>>          Or [<<Select1>>,<<Select2>>]
+## CHECK-DAG:                            Return [<<Or>>]
+
+## CHECK-START: boolean SmaliTests.$opt$noinline$booleanOrToAndV2(boolean, boolean) instruction_simplifier$after_gvn (after)
+## CHECK-DAG:       <<Cond1:z\d+>>       ParameterValue
+## CHECK-DAG:       <<Cond2:z\d+>>       ParameterValue
+## CHECK-DAG:       <<And:i\d+>>         And [<<Cond1>>,<<Cond2>>]
+## CHECK-DAG:       <<BooleanNot:z\d+>>  BooleanNot [<<And>>]
+## CHECK-DAG:                            Return [<<BooleanNot>>]
+
+## CHECK-START: boolean SmaliTests.$opt$noinline$booleanOrToAndV2(boolean, boolean) instruction_simplifier$after_bce (after)
+## CHECK-DAG:                            BooleanNot
+## CHECK-NOT:                            BooleanNot
+
+## CHECK-START: boolean SmaliTests.$opt$noinline$booleanOrToAndV2(boolean, boolean) instruction_simplifier$after_bce (after)
+## CHECK-NOT:                            Or
+
+# Original java source:
+#
+#     public static boolean $opt$noinline$booleanOrToAnd(boolean a, boolean b) {
+#       if (doThrow) throw new Error();
+#       return !a | !b;
+#     }
+
+.method public static $opt$noinline$booleanOrToAndV2(ZZ)Z
+    .registers 5
+    .param p0, "a"    # Z
+    .param p1, "b"    # Z
+
+    .prologue
+    const/4 v0, 0x1
+
+    const/4 v1, 0x0
+
+    .line 193
+    sget-boolean v2, LMain;->doThrow:Z
+
+    if-eqz v2, :cond_c
+
+    new-instance v0, Ljava/lang/Error;
+
+    invoke-direct {v0}, Ljava/lang/Error;-><init>()V
+
+    throw v0
+
+    .line 194
+    :cond_c
+    if-nez p0, :cond_13
+
+    move v2, v0
+
+    :goto_f
+    if-nez p1, :cond_15
+
+    :goto_11
+    or-int/2addr v0, v2
+
+    return v0
+
+    :cond_13
+    move v2, v1
+
+    goto :goto_f
+
+    :cond_15
+    move v0, v1
+
+    goto :goto_11
+.end method
+
+
+# Test that the transformation copes with inputs being separated from the
+# bitwise operations.
+# This is a regression test. The initial logic was inserting the new bitwise
+# operation incorrectly.
+
+## CHECK-START: int SmaliTests.$opt$noinline$regressInputsAwayV2(int, int) instruction_simplifier (before)
+## CHECK-DAG:       <<P1:i\d+>>          ParameterValue
+## CHECK-DAG:       <<P2:i\d+>>          ParameterValue
+## CHECK-DAG:       <<Cst1:i\d+>>        IntConstant 1
+## CHECK-DAG:       <<CstM1:i\d+>>       IntConstant -1
+## CHECK-DAG:       <<AddP1:i\d+>>       Add [<<P1>>,<<Cst1>>]
+## CHECK-DAG:       <<Not1:i\d+>>        Xor [<<AddP1>>,<<CstM1>>]
+## CHECK-DAG:       <<AddP2:i\d+>>       Add [<<P2>>,<<Cst1>>]
+## CHECK-DAG:       <<Not2:i\d+>>        Xor [<<AddP2>>,<<CstM1>>]
+## CHECK-DAG:       <<Or:i\d+>>          Or [<<Not1>>,<<Not2>>]
+## CHECK-DAG:                            Return [<<Or>>]
+
+## CHECK-START: int SmaliTests.$opt$noinline$regressInputsAwayV2(int, int) instruction_simplifier (after)
+## CHECK-DAG:       <<P1:i\d+>>          ParameterValue
+## CHECK-DAG:       <<P2:i\d+>>          ParameterValue
+## CHECK-DAG:       <<Cst1:i\d+>>        IntConstant 1
+## CHECK-DAG:       <<AddP1:i\d+>>       Add [<<P1>>,<<Cst1>>]
+## CHECK-DAG:       <<AddP2:i\d+>>       Add [<<P2>>,<<Cst1>>]
+## CHECK-DAG:       <<And:i\d+>>         And [<<AddP1>>,<<AddP2>>]
+## CHECK-DAG:       <<Not:i\d+>>         Not [<<And>>]
+## CHECK-DAG:                            Return [<<Not>>]
+
+## CHECK-START: int SmaliTests.$opt$noinline$regressInputsAwayV2(int, int) instruction_simplifier (after)
+## CHECK-DAG:                            Not
+## CHECK-NOT:                            Not
+
+## CHECK-START: int SmaliTests.$opt$noinline$regressInputsAwayV2(int, int) instruction_simplifier (after)
+## CHECK-NOT:                            Or
+
+# Original java source:
+#
+#     public static int $opt$noinline$regressInputsAway(int a, int b) {
+#       if (doThrow) throw new Error();
+#       int a1 = a + 1;
+#       int not_a1 = ~a1;
+#       int b1 = b + 1;
+#       int not_b1 = ~b1;
+#       return not_a1 | not_b1;
+#     }
+
+.method public static $opt$noinline$regressInputsAwayV2(II)I
+    .registers 7
+    .param p0, "a"    # I
+    .param p1, "b"    # I
+
+    .prologue
+    .line 234
+    sget-boolean v4, LMain;->doThrow:Z
+
+    if-eqz v4, :cond_a
+
+    new-instance v4, Ljava/lang/Error;
+
+    invoke-direct {v4}, Ljava/lang/Error;-><init>()V
+
+    throw v4
+
+    .line 235
+    :cond_a
+    add-int/lit8 v0, p0, 0x1
+
+    .line 236
+    .local v0, "a1":I
+    xor-int/lit8 v2, v0, -0x1
+
+    .line 237
+    .local v2, "not_a1":I
+    add-int/lit8 v1, p1, 0x1
+
+    .line 238
+    .local v1, "b1":I
+    xor-int/lit8 v3, v1, -0x1
+
+    .line 239
+    .local v3, "not_b1":I
+    or-int v4, v2, v3
+
+    return v4
+.end method
+
+
+# Test transformation of Not/Not/Xor into Xor.
+
+# See first note above.
+## CHECK-START: int SmaliTests.$opt$noinline$notXorToXorV2(int, int) instruction_simplifier (before)
+## CHECK-DAG:       <<P1:i\d+>>          ParameterValue
+## CHECK-DAG:       <<P2:i\d+>>          ParameterValue
+## CHECK-DAG:       <<CstM1:i\d+>>       IntConstant -1
+## CHECK-DAG:       <<Not1:i\d+>>        Xor [<<P1>>,<<CstM1>>]
+## CHECK-DAG:       <<Not2:i\d+>>        Xor [<<P2>>,<<CstM1>>]
+## CHECK-DAG:       <<Xor:i\d+>>         Xor [<<Not1>>,<<Not2>>]
+## CHECK-DAG:                            Return [<<Xor>>]
+
+## CHECK-START: int SmaliTests.$opt$noinline$notXorToXorV2(int, int) instruction_simplifier (after)
+## CHECK-DAG:       <<P1:i\d+>>          ParameterValue
+## CHECK-DAG:       <<P2:i\d+>>          ParameterValue
+## CHECK-DAG:       <<Xor:i\d+>>         Xor [<<P1>>,<<P2>>]
+## CHECK-DAG:                            Return [<<Xor>>]
+
+## CHECK-START: int SmaliTests.$opt$noinline$notXorToXorV2(int, int) instruction_simplifier (after)
+## CHECK-NOT:                            Not
+
+# Original java source:
+#
+#     public static int $opt$noinline$notXorToXor(int a, int b) {
+#       if (doThrow) throw new Error();
+#       return ~a ^ ~b;
+#     }
+
+.method public static $opt$noinline$notXorToXorV2(II)I
+    .registers 4
+    .param p0, "a"    # I
+    .param p1, "b"    # I
+
+    .prologue
+    .line 266
+    sget-boolean v0, LMain;->doThrow:Z
+
+    if-eqz v0, :cond_a
+
+    new-instance v0, Ljava/lang/Error;
+
+    invoke-direct {v0}, Ljava/lang/Error;-><init>()V
+
+    throw v0
+
+    .line 267
+    :cond_a
+    xor-int/lit8 v0, p0, -0x1
+
+    xor-int/lit8 v1, p1, -0x1
+
+    xor-int/2addr v0, v1
+
+    return v0
+.end method
+
+
+# Test transformation of Not/Not/Xor into Xor for boolean negations.
+# Note that the graph before this instruction simplification pass does not
+# contain `HBooleanNot` instructions. This is because this transformation
+# follows the optimization of `HSelect` to `HBooleanNot` occurring in the
+# same pass.
+
+## CHECK-START: boolean SmaliTests.$opt$noinline$booleanNotXorToXorV2(boolean, boolean) instruction_simplifier$after_gvn (before)
+## CHECK-DAG:       <<P1:z\d+>>          ParameterValue
+## CHECK-DAG:       <<P2:z\d+>>          ParameterValue
+## CHECK-DAG:       <<Const0:i\d+>>      IntConstant 0
+## CHECK-DAG:       <<Const1:i\d+>>      IntConstant 1
+## CHECK-DAG:       <<Select1:i\d+>>     Select [<<Const1>>,<<Const0>>,<<P1>>]
+## CHECK-DAG:       <<Select2:i\d+>>     Select [<<Const1>>,<<Const0>>,<<P2>>]
+## CHECK-DAG:       <<Xor:i\d+>>         Xor [<<Select1>>,<<Select2>>]
+## CHECK-DAG:                            Return [<<Xor>>]
+
+## CHECK-START: boolean SmaliTests.$opt$noinline$booleanNotXorToXorV2(boolean, boolean) instruction_simplifier$after_gvn (after)
+## CHECK-DAG:       <<Cond1:z\d+>>       ParameterValue
+## CHECK-DAG:       <<Cond2:z\d+>>       ParameterValue
+## CHECK-DAG:       <<Xor:i\d+>>         Xor [<<Cond1>>,<<Cond2>>]
+## CHECK-DAG:                            Return [<<Xor>>]
+
+## CHECK-START: boolean SmaliTests.$opt$noinline$booleanNotXorToXorV2(boolean, boolean) instruction_simplifier$after_bce (after)
+## CHECK-NOT:                            BooleanNot
+
+# Original java source:
+#
+#     public static boolean $opt$noinline$booleanNotXorToXor(boolean a, boolean b) {
+#       if (doThrow) throw new Error();
+#       return !a ^ !b;
+#     }
+
+.method public static $opt$noinline$booleanNotXorToXorV2(ZZ)Z
+    .registers 5
+    .param p0, "a"    # Z
+    .param p1, "b"    # Z
+
+    .prologue
+    const/4 v0, 0x1
+
+    const/4 v1, 0x0
+
+    .line 298
+    sget-boolean v2, LMain;->doThrow:Z
+
+    if-eqz v2, :cond_c
+
+    new-instance v0, Ljava/lang/Error;
+
+    invoke-direct {v0}, Ljava/lang/Error;-><init>()V
+
+    throw v0
+
+    .line 299
+    :cond_c
+    if-nez p0, :cond_13
+
+    move v2, v0
+
+    :goto_f
+    if-nez p1, :cond_15
+
+    :goto_11
+    xor-int/2addr v0, v2
+
+    return v0
+
+    :cond_13
+    move v2, v1
+
+    goto :goto_f
+
+    :cond_15
+    move v0, v1
+
+    goto :goto_11
+.end method
+
+
+# Check that no transformation is done when one Not has multiple uses.
+
+## CHECK-START: int SmaliTests.$opt$noinline$notMultipleUsesV2(int, int) instruction_simplifier (before)
+## CHECK-DAG:       <<P1:i\d+>>          ParameterValue
+## CHECK-DAG:       <<P2:i\d+>>          ParameterValue
+## CHECK-DAG:       <<CstM1:i\d+>>       IntConstant -1
+## CHECK-DAG:       <<One:i\d+>>         IntConstant 1
+## CHECK-DAG:       <<Not2:i\d+>>        Xor [<<P2>>,<<CstM1>>]
+## CHECK-DAG:       <<And2:i\d+>>        And [<<Not2>>,<<One>>]
+## CHECK-DAG:       <<Not1:i\d+>>        Xor [<<P1>>,<<CstM1>>]
+## CHECK-DAG:       <<And1:i\d+>>        And [<<Not1>>,<<Not2>>]
+## CHECK-DAG:       <<Add:i\d+>>         Add [<<And2>>,<<And1>>]
+## CHECK-DAG:                            Return [<<Add>>]
+
+## CHECK-START: int SmaliTests.$opt$noinline$notMultipleUsesV2(int, int) instruction_simplifier (after)
+## CHECK-DAG:       <<P1:i\d+>>          ParameterValue
+## CHECK-DAG:       <<P2:i\d+>>          ParameterValue
+## CHECK-DAG:       <<One:i\d+>>         IntConstant 1
+## CHECK-DAG:       <<Not2:i\d+>>        Not [<<P2>>]
+## CHECK-DAG:       <<And2:i\d+>>        And [<<Not2>>,<<One>>]
+## CHECK-DAG:       <<Not1:i\d+>>        Not [<<P1>>]
+## CHECK-DAG:       <<And1:i\d+>>        And [<<Not1>>,<<Not2>>]
+## CHECK-DAG:       <<Add:i\d+>>         Add [<<And2>>,<<And1>>]
+## CHECK-DAG:                            Return [<<Add>>]
+
+## CHECK-START: int SmaliTests.$opt$noinline$notMultipleUsesV2(int, int) instruction_simplifier (after)
+## CHECK-NOT:                            Or
+
+# Original java source:
+#
+#     public static int $opt$noinline$notMultipleUses(int a, int b) {
+#       if (doThrow) throw new Error();
+#       int tmp = ~b;
+#       return (tmp & 0x1) + (~a & tmp);
+#     }
+
+.method public static $opt$noinline$notMultipleUsesV2(II)I
+    .registers 5
+    .param p0, "a"    # I
+    .param p1, "b"    # I
+
+    .prologue
+    .line 333
+    sget-boolean v1, LMain;->doThrow:Z
+
+    if-eqz v1, :cond_a
+
+    new-instance v1, Ljava/lang/Error;
+
+    invoke-direct {v1}, Ljava/lang/Error;-><init>()V
+
+    throw v1
+
+    .line 334
+    :cond_a
+    xor-int/lit8 v0, p1, -0x1
+
+    .line 335
+    .local v0, "tmp":I
+    and-int/lit8 v1, v0, 0x1
+
+    xor-int/lit8 v2, p0, -0x1
+
+    and-int/2addr v2, v0
+
+    add-int/2addr v1, v2
+
+    return v1
+.end method
diff --git a/test/565-checker-doublenegbitwise/src/Main.java b/test/565-checker-doublenegbitwise/src/Main.java
index e36a2ba..5121569 100644
--- a/test/565-checker-doublenegbitwise/src/Main.java
+++ b/test/565-checker-doublenegbitwise/src/Main.java
@@ -52,305 +52,22 @@
     }
   }
 
-  /**
-   * Test transformation of Not/Not/And into Or/Not.
-   */
-
-  // Note: before the instruction_simplifier pass, Xor's are used instead of
-  // Not's (the simplification happens during the same pass).
-  /// CHECK-START: int Main.$opt$noinline$andToOr(int, int) instruction_simplifier (before)
-  /// CHECK-DAG:       <<P1:i\d+>>          ParameterValue
-  /// CHECK-DAG:       <<P2:i\d+>>          ParameterValue
-  /// CHECK-DAG:       <<CstM1:i\d+>>       IntConstant -1
-  /// CHECK-DAG:       <<Not1:i\d+>>        Xor [<<P1>>,<<CstM1>>]
-  /// CHECK-DAG:       <<Not2:i\d+>>        Xor [<<P2>>,<<CstM1>>]
-  /// CHECK-DAG:       <<And:i\d+>>         And [<<Not1>>,<<Not2>>]
-  /// CHECK-DAG:                            Return [<<And>>]
-
-  /// CHECK-START: int Main.$opt$noinline$andToOr(int, int) instruction_simplifier (after)
-  /// CHECK-DAG:       <<P1:i\d+>>          ParameterValue
-  /// CHECK-DAG:       <<P2:i\d+>>          ParameterValue
-  /// CHECK-DAG:       <<Or:i\d+>>          Or [<<P1>>,<<P2>>]
-  /// CHECK-DAG:       <<Not:i\d+>>         Not [<<Or>>]
-  /// CHECK-DAG:                            Return [<<Not>>]
-
-  /// CHECK-START: int Main.$opt$noinline$andToOr(int, int) instruction_simplifier (after)
-  /// CHECK-DAG:                            Not
-  /// CHECK-NOT:                            Not
-
-  /// CHECK-START: int Main.$opt$noinline$andToOr(int, int) instruction_simplifier (after)
-  /// CHECK-NOT:                            And
-
-  public static int $opt$noinline$andToOr(int a, int b) {
-    if (doThrow) throw new Error();
-    return ~a & ~b;
-  }
-
-  /**
-   * Test transformation of Not/Not/And into Or/Not for boolean negations.
-   * Note that the graph before this instruction simplification pass does not
-   * contain `HBooleanNot` instructions. This is because this transformation
-   * follows the optimization of `HSelect` to `HBooleanNot` occurring in the
-   * same pass.
-   */
-
-  /// CHECK-START: boolean Main.$opt$noinline$booleanAndToOr(boolean, boolean) instruction_simplifier$after_gvn (before)
-  /// CHECK-DAG:       <<P1:z\d+>>          ParameterValue
-  /// CHECK-DAG:       <<P2:z\d+>>          ParameterValue
-  /// CHECK-DAG:       <<Const0:i\d+>>      IntConstant 0
-  /// CHECK-DAG:       <<Const1:i\d+>>      IntConstant 1
-  /// CHECK-DAG:       <<Select1:i\d+>>     Select [<<Const1>>,<<Const0>>,<<P1>>]
-  /// CHECK-DAG:       <<Select2:i\d+>>     Select [<<Const1>>,<<Const0>>,<<P2>>]
-  /// CHECK-DAG:       <<And:i\d+>>         And [<<Select1>>,<<Select2>>]
-  /// CHECK-DAG:                            Return [<<And>>]
-
-  /// CHECK-START: boolean Main.$opt$noinline$booleanAndToOr(boolean, boolean) instruction_simplifier$after_gvn (after)
-  /// CHECK-DAG:       <<Cond1:z\d+>>       ParameterValue
-  /// CHECK-DAG:       <<Cond2:z\d+>>       ParameterValue
-  /// CHECK-DAG:       <<Or:i\d+>>          Or [<<Cond1>>,<<Cond2>>]
-  /// CHECK-DAG:       <<BooleanNot:z\d+>>  BooleanNot [<<Or>>]
-  /// CHECK-DAG:                            Return [<<BooleanNot>>]
-
-  /// CHECK-START: boolean Main.$opt$noinline$booleanAndToOr(boolean, boolean) instruction_simplifier$after_bce (after)
-  /// CHECK-DAG:                            BooleanNot
-  /// CHECK-NOT:                            BooleanNot
-
-  /// CHECK-START: boolean Main.$opt$noinline$booleanAndToOr(boolean, boolean) instruction_simplifier$after_bce (after)
-  /// CHECK-NOT:                            And
-
-  public static boolean $opt$noinline$booleanAndToOr(boolean a, boolean b) {
-    if (doThrow) throw new Error();
-    return !a & !b;
-  }
-
-  /**
-   * Test transformation of Not/Not/Or into And/Not.
-   */
-
-  // See note above.
-  // The second Xor has its arguments reversed for no obvious reason.
-  /// CHECK-START: long Main.$opt$noinline$orToAnd(long, long) instruction_simplifier (before)
-  /// CHECK-DAG:       <<P1:j\d+>>          ParameterValue
-  /// CHECK-DAG:       <<P2:j\d+>>          ParameterValue
-  /// CHECK-DAG:       <<CstM1:j\d+>>       LongConstant -1
-  /// CHECK-DAG:       <<Not1:j\d+>>        Xor [<<P1>>,<<CstM1>>]
-  /// CHECK-DAG:       <<Not2:j\d+>>        Xor [<<CstM1>>,<<P2>>]
-  /// CHECK-DAG:       <<Or:j\d+>>          Or [<<Not1>>,<<Not2>>]
-  /// CHECK-DAG:                            Return [<<Or>>]
-
-  /// CHECK-START: long Main.$opt$noinline$orToAnd(long, long) instruction_simplifier (after)
-  /// CHECK-DAG:       <<P1:j\d+>>          ParameterValue
-  /// CHECK-DAG:       <<P2:j\d+>>          ParameterValue
-  /// CHECK-DAG:       <<And:j\d+>>         And [<<P1>>,<<P2>>]
-  /// CHECK-DAG:       <<Not:j\d+>>         Not [<<And>>]
-  /// CHECK-DAG:                            Return [<<Not>>]
-
-  /// CHECK-START: long Main.$opt$noinline$orToAnd(long, long) instruction_simplifier (after)
-  /// CHECK-DAG:                            Not
-  /// CHECK-NOT:                            Not
-
-  /// CHECK-START: long Main.$opt$noinline$orToAnd(long, long) instruction_simplifier (after)
-  /// CHECK-NOT:                            Or
-
-  public static long $opt$noinline$orToAnd(long a, long b) {
-    if (doThrow) throw new Error();
-    return ~a | ~b;
-  }
-
-  /**
-   * Test transformation of Not/Not/Or into Or/And for boolean negations.
-   * Note that the graph before this instruction simplification pass does not
-   * contain `HBooleanNot` instructions. This is because this transformation
-   * follows the optimization of `HSelect` to `HBooleanNot` occurring in the
-   * same pass.
-   */
-
-  /// CHECK-START: boolean Main.$opt$noinline$booleanOrToAnd(boolean, boolean) instruction_simplifier$after_gvn (before)
-  /// CHECK-DAG:       <<P1:z\d+>>          ParameterValue
-  /// CHECK-DAG:       <<P2:z\d+>>          ParameterValue
-  /// CHECK-DAG:       <<Const0:i\d+>>      IntConstant 0
-  /// CHECK-DAG:       <<Const1:i\d+>>      IntConstant 1
-  /// CHECK-DAG:       <<Select1:i\d+>>     Select [<<Const1>>,<<Const0>>,<<P1>>]
-  /// CHECK-DAG:       <<Select2:i\d+>>     Select [<<Const1>>,<<Const0>>,<<P2>>]
-  /// CHECK-DAG:       <<Or:i\d+>>          Or [<<Select1>>,<<Select2>>]
-  /// CHECK-DAG:                            Return [<<Or>>]
-
-  /// CHECK-START: boolean Main.$opt$noinline$booleanOrToAnd(boolean, boolean) instruction_simplifier$after_gvn (after)
-  /// CHECK-DAG:       <<Cond1:z\d+>>       ParameterValue
-  /// CHECK-DAG:       <<Cond2:z\d+>>       ParameterValue
-  /// CHECK-DAG:       <<And:i\d+>>         And [<<Cond1>>,<<Cond2>>]
-  /// CHECK-DAG:       <<BooleanNot:z\d+>>  BooleanNot [<<And>>]
-  /// CHECK-DAG:                            Return [<<BooleanNot>>]
-
-  /// CHECK-START: boolean Main.$opt$noinline$booleanOrToAnd(boolean, boolean) instruction_simplifier$after_bce (after)
-  /// CHECK-DAG:                            BooleanNot
-  /// CHECK-NOT:                            BooleanNot
-
-  /// CHECK-START: boolean Main.$opt$noinline$booleanOrToAnd(boolean, boolean) instruction_simplifier$after_bce (after)
-  /// CHECK-NOT:                            Or
-
-  public static boolean $opt$noinline$booleanOrToAnd(boolean a, boolean b) {
-    if (doThrow) throw new Error();
-    return !a | !b;
-  }
-
-  /**
-   * Test that the transformation copes with inputs being separated from the
-   * bitwise operations.
-   * This is a regression test. The initial logic was inserting the new bitwise
-   * operation incorrectly.
-   */
-
-  /// CHECK-START: int Main.$opt$noinline$regressInputsAway(int, int) instruction_simplifier (before)
-  /// CHECK-DAG:       <<P1:i\d+>>          ParameterValue
-  /// CHECK-DAG:       <<P2:i\d+>>          ParameterValue
-  /// CHECK-DAG:       <<Cst1:i\d+>>        IntConstant 1
-  /// CHECK-DAG:       <<CstM1:i\d+>>       IntConstant -1
-  /// CHECK-DAG:       <<AddP1:i\d+>>       Add [<<P1>>,<<Cst1>>]
-  /// CHECK-DAG:       <<Not1:i\d+>>        Xor [<<AddP1>>,<<CstM1>>]
-  /// CHECK-DAG:       <<AddP2:i\d+>>       Add [<<P2>>,<<Cst1>>]
-  /// CHECK-DAG:       <<Not2:i\d+>>        Xor [<<AddP2>>,<<CstM1>>]
-  /// CHECK-DAG:       <<Or:i\d+>>          Or [<<Not1>>,<<Not2>>]
-  /// CHECK-DAG:                            Return [<<Or>>]
-
-  /// CHECK-START: int Main.$opt$noinline$regressInputsAway(int, int) instruction_simplifier (after)
-  /// CHECK-DAG:       <<P1:i\d+>>          ParameterValue
-  /// CHECK-DAG:       <<P2:i\d+>>          ParameterValue
-  /// CHECK-DAG:       <<Cst1:i\d+>>        IntConstant 1
-  /// CHECK-DAG:       <<AddP1:i\d+>>       Add [<<P1>>,<<Cst1>>]
-  /// CHECK-DAG:       <<AddP2:i\d+>>       Add [<<P2>>,<<Cst1>>]
-  /// CHECK-DAG:       <<And:i\d+>>         And [<<AddP1>>,<<AddP2>>]
-  /// CHECK-DAG:       <<Not:i\d+>>         Not [<<And>>]
-  /// CHECK-DAG:                            Return [<<Not>>]
-
-  /// CHECK-START: int Main.$opt$noinline$regressInputsAway(int, int) instruction_simplifier (after)
-  /// CHECK-DAG:                            Not
-  /// CHECK-NOT:                            Not
-
-  /// CHECK-START: int Main.$opt$noinline$regressInputsAway(int, int) instruction_simplifier (after)
-  /// CHECK-NOT:                            Or
-
-  public static int $opt$noinline$regressInputsAway(int a, int b) {
-    if (doThrow) throw new Error();
-    int a1 = a + 1;
-    int not_a1 = ~a1;
-    int b1 = b + 1;
-    int not_b1 = ~b1;
-    return not_a1 | not_b1;
-  }
-
-  /**
-   * Test transformation of Not/Not/Xor into Xor.
-   */
-
-  // See first note above.
-  /// CHECK-START: int Main.$opt$noinline$notXorToXor(int, int) instruction_simplifier (before)
-  /// CHECK-DAG:       <<P1:i\d+>>          ParameterValue
-  /// CHECK-DAG:       <<P2:i\d+>>          ParameterValue
-  /// CHECK-DAG:       <<CstM1:i\d+>>       IntConstant -1
-  /// CHECK-DAG:       <<Not1:i\d+>>        Xor [<<P1>>,<<CstM1>>]
-  /// CHECK-DAG:       <<Not2:i\d+>>        Xor [<<P2>>,<<CstM1>>]
-  /// CHECK-DAG:       <<Xor:i\d+>>         Xor [<<Not1>>,<<Not2>>]
-  /// CHECK-DAG:                            Return [<<Xor>>]
-
-  /// CHECK-START: int Main.$opt$noinline$notXorToXor(int, int) instruction_simplifier (after)
-  /// CHECK-DAG:       <<P1:i\d+>>          ParameterValue
-  /// CHECK-DAG:       <<P2:i\d+>>          ParameterValue
-  /// CHECK-DAG:       <<Xor:i\d+>>         Xor [<<P1>>,<<P2>>]
-  /// CHECK-DAG:                            Return [<<Xor>>]
-
-  /// CHECK-START: int Main.$opt$noinline$notXorToXor(int, int) instruction_simplifier (after)
-  /// CHECK-NOT:                            Not
-
-  public static int $opt$noinline$notXorToXor(int a, int b) {
-    if (doThrow) throw new Error();
-    return ~a ^ ~b;
-  }
-
-  /**
-   * Test transformation of Not/Not/Xor into Xor for boolean negations.
-   * Note that the graph before this instruction simplification pass does not
-   * contain `HBooleanNot` instructions. This is because this transformation
-   * follows the optimization of `HSelect` to `HBooleanNot` occurring in the
-   * same pass.
-   */
-
-  /// CHECK-START: boolean Main.$opt$noinline$booleanNotXorToXor(boolean, boolean) instruction_simplifier$after_gvn (before)
-  /// CHECK-DAG:       <<P1:z\d+>>          ParameterValue
-  /// CHECK-DAG:       <<P2:z\d+>>          ParameterValue
-  /// CHECK-DAG:       <<Const0:i\d+>>      IntConstant 0
-  /// CHECK-DAG:       <<Const1:i\d+>>      IntConstant 1
-  /// CHECK-DAG:       <<Select1:i\d+>>     Select [<<Const1>>,<<Const0>>,<<P1>>]
-  /// CHECK-DAG:       <<Select2:i\d+>>     Select [<<Const1>>,<<Const0>>,<<P2>>]
-  /// CHECK-DAG:       <<Xor:i\d+>>         Xor [<<Select1>>,<<Select2>>]
-  /// CHECK-DAG:                            Return [<<Xor>>]
-
-  /// CHECK-START: boolean Main.$opt$noinline$booleanNotXorToXor(boolean, boolean) instruction_simplifier$after_gvn (after)
-  /// CHECK-DAG:       <<Cond1:z\d+>>       ParameterValue
-  /// CHECK-DAG:       <<Cond2:z\d+>>       ParameterValue
-  /// CHECK-DAG:       <<Xor:i\d+>>         Xor [<<Cond1>>,<<Cond2>>]
-  /// CHECK-DAG:                            Return [<<Xor>>]
-
-  /// CHECK-START: boolean Main.$opt$noinline$booleanNotXorToXor(boolean, boolean) instruction_simplifier$after_bce (after)
-  /// CHECK-NOT:                            BooleanNot
-
-  public static boolean $opt$noinline$booleanNotXorToXor(boolean a, boolean b) {
-    if (doThrow) throw new Error();
-    return !a ^ !b;
-  }
-
-  /**
-   * Check that no transformation is done when one Not has multiple uses.
-   */
-
-  /// CHECK-START: int Main.$opt$noinline$notMultipleUses(int, int) instruction_simplifier (before)
-  /// CHECK-DAG:       <<P1:i\d+>>          ParameterValue
-  /// CHECK-DAG:       <<P2:i\d+>>          ParameterValue
-  /// CHECK-DAG:       <<CstM1:i\d+>>       IntConstant -1
-  /// CHECK-DAG:       <<One:i\d+>>         IntConstant 1
-  /// CHECK-DAG:       <<Not2:i\d+>>        Xor [<<P2>>,<<CstM1>>]
-  /// CHECK-DAG:       <<And2:i\d+>>        And [<<Not2>>,<<One>>]
-  /// CHECK-DAG:       <<Not1:i\d+>>        Xor [<<P1>>,<<CstM1>>]
-  /// CHECK-DAG:       <<And1:i\d+>>        And [<<Not1>>,<<Not2>>]
-  /// CHECK-DAG:       <<Add:i\d+>>         Add [<<And2>>,<<And1>>]
-  /// CHECK-DAG:                            Return [<<Add>>]
-
-  /// CHECK-START: int Main.$opt$noinline$notMultipleUses(int, int) instruction_simplifier (after)
-  /// CHECK-DAG:       <<P1:i\d+>>          ParameterValue
-  /// CHECK-DAG:       <<P2:i\d+>>          ParameterValue
-  /// CHECK-DAG:       <<One:i\d+>>         IntConstant 1
-  /// CHECK-DAG:       <<Not2:i\d+>>        Not [<<P2>>]
-  /// CHECK-DAG:       <<And2:i\d+>>        And [<<Not2>>,<<One>>]
-  /// CHECK-DAG:       <<Not1:i\d+>>        Not [<<P1>>]
-  /// CHECK-DAG:       <<And1:i\d+>>        And [<<Not1>>,<<Not2>>]
-  /// CHECK-DAG:       <<Add:i\d+>>         Add [<<And2>>,<<And1>>]
-  /// CHECK-DAG:                            Return [<<Add>>]
-
-  /// CHECK-START: int Main.$opt$noinline$notMultipleUses(int, int) instruction_simplifier (after)
-  /// CHECK-NOT:                            Or
-
-  public static int $opt$noinline$notMultipleUses(int a, int b) {
-    if (doThrow) throw new Error();
-    int tmp = ~b;
-    return (tmp & 0x1) + (~a & tmp);
-  }
-
-  public static void main(String[] args) {
-    assertIntEquals(~0xff, $opt$noinline$andToOr(0xf, 0xff));
+  public static void main(String[] args) throws Exception {
+    assertIntEquals(~0xff, $noinline$runSmaliTest("$opt$noinline$andToOrV2", int.class, 0xf, 0xff));
     assertIntEquals(~0xff, $noinline$runSmaliTest("$opt$noinline$andToOr", int.class, 0xf, 0xff));
-    assertEquals(true, $opt$noinline$booleanAndToOr(false, false));
+    assertEquals(true, $noinline$runSmaliTest("$opt$noinline$booleanAndToOrV2", boolean.class, false, false));
     assertEquals(true, $noinline$runSmaliTest("$opt$noinline$booleanAndToOr", boolean.class, false, false));
-    assertLongEquals(~0xf, $opt$noinline$orToAnd(0xf, 0xff));
+    assertLongEquals(~0xf, $noinline$runSmaliTest("$opt$noinline$orToAndV2", long.class, 0xfL, 0xffL));
     assertLongEquals(~0xf, $noinline$runSmaliTest("$opt$noinline$orToAnd", long.class, 0xfL, 0xffL));
-    assertEquals(false, $opt$noinline$booleanOrToAnd(true, true));
+    assertEquals(false, $noinline$runSmaliTest("$opt$noinline$booleanOrToAndV2", boolean.class, true, true));
     assertEquals(false, $noinline$runSmaliTest("$opt$noinline$booleanOrToAnd", boolean.class, true, true));
-    assertIntEquals(-1, $opt$noinline$regressInputsAway(0xf, 0xff));
+    assertIntEquals(-1, $noinline$runSmaliTest("$opt$noinline$regressInputsAwayV2", int.class, 0xf, 0xff));
     assertIntEquals(-1, $noinline$runSmaliTest("$opt$noinline$regressInputsAway", int.class, 0xf, 0xff));
-    assertIntEquals(0xf0, $opt$noinline$notXorToXor(0xf, 0xff));
+    assertIntEquals(0xf0, $noinline$runSmaliTest("$opt$noinline$notXorToXorV2", int.class, 0xf, 0xff));
     assertIntEquals(0xf0, $noinline$runSmaliTest("$opt$noinline$notXorToXor", int.class, 0xf, 0xff));
-    assertEquals(true, $opt$noinline$booleanNotXorToXor(true, false));
+    assertEquals(true, $noinline$runSmaliTest("$opt$noinline$booleanNotXorToXorV2", boolean.class, true, false));
     assertEquals(true, $noinline$runSmaliTest("$opt$noinline$booleanNotXorToXor", boolean.class, true, false));
-    assertIntEquals(~0xff, $opt$noinline$notMultipleUses(0xf, 0xff));
+    assertIntEquals(~0xff, $noinline$runSmaliTest("$opt$noinline$notMultipleUsesV2", int.class, 0xf, 0xff));
     assertIntEquals(~0xff, $noinline$runSmaliTest("$opt$noinline$notMultipleUses", int.class, 0xf, 0xff));
   }
 }
diff --git a/test/952-invoke-custom/src/TestReturnValues.java b/test/952-invoke-custom/src/TestReturnValues.java
new file mode 100644
index 0000000..8450a44
--- /dev/null
+++ b/test/952-invoke-custom/src/TestReturnValues.java
@@ -0,0 +1,330 @@
+/*
+ * Copyright (C) 2018 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 annotations.BootstrapMethod;
+import annotations.CalledByIndy;
+import java.lang.invoke.CallSite;
+import java.lang.invoke.ConstantCallSite;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+
+class TestReturnValues extends TestBase {
+    static CallSite bsm(MethodHandles.Lookup lookup, String name, MethodType methodType)
+            throws Throwable {
+        MethodHandle mh = lookup.findStatic(TestReturnValues.class, name, methodType);
+        return new ConstantCallSite(mh);
+    }
+
+    //
+    // Methods that pass through a single argument.
+    // Used to check return path.
+    //
+    static byte passThrough(byte value) {
+        return value;
+    }
+
+    static char passThrough(char value) {
+        return value;
+    }
+
+    static double passThrough(double value) {
+        return value;
+    }
+
+    static float passThrough(float value) {
+        return value;
+    }
+
+    static int passThrough(int value) {
+        return value;
+    }
+
+    static Object passThrough(Object value) {
+        return value;
+    }
+
+    static Object[] passThrough(Object[] value) {
+        return value;
+    }
+
+    static long passThrough(long value) {
+        return value;
+    }
+
+    static short passThrough(short value) {
+        return value;
+    }
+
+    static void passThrough() {}
+
+    static boolean passThrough(boolean value) {
+        return value;
+    }
+
+    // byte
+    @CalledByIndy(
+            bootstrapMethod =
+                    @BootstrapMethod(enclosingType = TestReturnValues.class, name = "bsm"),
+            fieldOrMethodName = "passThrough",
+            returnType = byte.class,
+            parameterTypes = {byte.class})
+    private static byte passThroughCallSite(byte value) {
+        assertNotReached();
+        return (byte) 0;
+    }
+
+    // char
+    @CalledByIndy(
+            bootstrapMethod =
+                    @BootstrapMethod(enclosingType = TestReturnValues.class, name = "bsm"),
+            fieldOrMethodName = "passThrough",
+            returnType = char.class,
+            parameterTypes = {char.class})
+    private static char passThroughCallSite(char value) {
+        assertNotReached();
+        return 'Z';
+    }
+
+    // double
+    @CalledByIndy(
+            bootstrapMethod =
+                    @BootstrapMethod(enclosingType = TestReturnValues.class, name = "bsm"),
+            fieldOrMethodName = "passThrough",
+            returnType = double.class,
+            parameterTypes = {double.class})
+    private static double passThroughCallSite(double value) {
+        assertNotReached();
+        return Double.NaN;
+    }
+
+    // float
+    @CalledByIndy(
+            bootstrapMethod =
+                    @BootstrapMethod(enclosingType = TestReturnValues.class, name = "bsm"),
+            fieldOrMethodName = "passThrough",
+            returnType = float.class,
+            parameterTypes = {float.class})
+    private static float passThroughCallSite(float value) {
+        assertNotReached();
+        return Float.NaN;
+    }
+
+    // int
+    @CalledByIndy(
+            bootstrapMethod =
+                    @BootstrapMethod(enclosingType = TestReturnValues.class, name = "bsm"),
+            fieldOrMethodName = "passThrough",
+            returnType = int.class,
+            parameterTypes = {int.class})
+    private static int passThroughCallSite(int value) {
+        assertNotReached();
+        return 0;
+    }
+
+    // long
+    @CalledByIndy(
+            bootstrapMethod =
+                    @BootstrapMethod(enclosingType = TestReturnValues.class, name = "bsm"),
+            fieldOrMethodName = "passThrough",
+            returnType = long.class,
+            parameterTypes = {long.class})
+    private static long passThroughCallSite(long value) {
+        assertNotReached();
+        return Long.MIN_VALUE;
+    }
+
+    // Object
+    @CalledByIndy(
+            bootstrapMethod =
+                    @BootstrapMethod(enclosingType = TestReturnValues.class, name = "bsm"),
+            fieldOrMethodName = "passThrough",
+            returnType = Object.class,
+            parameterTypes = {Object.class})
+    private static Object passThroughCallSite(Object value) {
+        assertNotReached();
+        return null;
+    }
+
+    // Object[]
+    @CalledByIndy(
+            bootstrapMethod =
+                    @BootstrapMethod(enclosingType = TestReturnValues.class, name = "bsm"),
+            fieldOrMethodName = "passThrough",
+            returnType = Object[].class,
+            parameterTypes = {Object[].class})
+    private static Object[] passThroughCallSite(Object[] value) {
+        assertNotReached();
+        return null;
+    }
+
+    // short
+    @CalledByIndy(
+            bootstrapMethod =
+                    @BootstrapMethod(enclosingType = TestReturnValues.class, name = "bsm"),
+            fieldOrMethodName = "passThrough",
+            returnType = short.class,
+            parameterTypes = {short.class})
+    private static short passThroughCallSite(short value) {
+        assertNotReached();
+        return (short) 0;
+    }
+
+    // void
+    @CalledByIndy(
+            bootstrapMethod =
+                    @BootstrapMethod(enclosingType = TestReturnValues.class, name = "bsm"),
+            fieldOrMethodName = "passThrough",
+            returnType = void.class,
+            parameterTypes = {})
+    private static void passThroughCallSite() {
+        assertNotReached();
+    }
+
+    // boolean
+    @CalledByIndy(
+            bootstrapMethod =
+                    @BootstrapMethod(enclosingType = TestReturnValues.class, name = "bsm"),
+            fieldOrMethodName = "passThrough",
+            returnType = boolean.class,
+            parameterTypes = {boolean.class})
+    private static boolean passThroughCallSite(boolean value) {
+        assertNotReached();
+        return false;
+    }
+
+    private static void testByteReturnValues() {
+        byte[] values = {Byte.MIN_VALUE, Byte.MAX_VALUE};
+        for (byte value : values) {
+            assertEquals(value, (byte) passThroughCallSite(value));
+        }
+    }
+
+    private static void testCharReturnValues() {
+        char[] values = {
+            Character.MIN_VALUE,
+            Character.MAX_HIGH_SURROGATE,
+            Character.MAX_LOW_SURROGATE,
+            Character.MAX_VALUE
+        };
+        for (char value : values) {
+            assertEquals(value, (char) passThroughCallSite(value));
+        }
+    }
+
+    private static void testDoubleReturnValues() {
+        double[] values = {
+            Double.MIN_VALUE,
+            Double.MIN_NORMAL,
+            Double.NaN,
+            Double.POSITIVE_INFINITY,
+            Double.NEGATIVE_INFINITY,
+            Double.MAX_VALUE
+        };
+        for (double value : values) {
+            assertEquals(value, (double) passThroughCallSite(value));
+        }
+    }
+
+    private static void testFloatReturnValues() {
+        float[] values = {
+            Float.MIN_VALUE,
+            Float.MIN_NORMAL,
+            Float.NaN,
+            Float.POSITIVE_INFINITY,
+            Float.NEGATIVE_INFINITY,
+            Float.MAX_VALUE
+        };
+        for (float value : values) {
+            assertEquals(value, (float) passThroughCallSite(value));
+        }
+    }
+
+    private static void testIntReturnValues() {
+        int[] values = {Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.SIZE, -Integer.SIZE};
+        for (int value : values) {
+            assertEquals(value, (int) passThroughCallSite(value));
+        }
+    }
+
+    private static void testLongReturnValues() {
+        long[] values = {Long.MIN_VALUE, Long.MAX_VALUE, (long) Long.SIZE, (long) -Long.SIZE};
+        for (long value : values) {
+            assertEquals(value, (long) passThroughCallSite(value));
+        }
+    }
+
+    private static void testObjectReturnValues() {
+        Object[] values = {null, "abc", Integer.valueOf(123)};
+        for (Object value : values) {
+            assertEquals(value, (Object) passThroughCallSite(value));
+        }
+
+        Object[] otherValues = (Object[]) passThroughCallSite(values);
+        assertEquals(values.length, otherValues.length);
+        for (int i = 0; i < otherValues.length; ++i) {
+            assertEquals(values[i], otherValues[i]);
+        }
+    }
+
+    private static void testShortReturnValues() {
+        short[] values = {
+            Short.MIN_VALUE, Short.MAX_VALUE, (short) Short.SIZE, (short) -Short.SIZE
+        };
+        for (short value : values) {
+            assertEquals(value, (short) passThroughCallSite(value));
+        }
+    }
+
+    private static void testVoidReturnValues() {
+        long l = Long.MIN_VALUE;
+        double d = Double.MIN_VALUE;
+        passThroughCallSite(); // Initializes call site
+        assertEquals(Long.MIN_VALUE, l);
+        assertEquals(Double.MIN_VALUE, d);
+
+        l = Long.MAX_VALUE;
+        d = Double.MAX_VALUE;
+        passThroughCallSite(); // re-uses existing call site
+        assertEquals(Long.MAX_VALUE, l);
+        assertEquals(Double.MAX_VALUE, d);
+    }
+
+    private static void testBooleanReturnValues() {
+        boolean[] values = {true, false, true, false, false};
+        for (boolean value : values) {
+            assertEquals(value, (boolean) passThroughCallSite(value));
+        }
+    }
+
+    public static void test() {
+        System.out.println(TestReturnValues.class.getName());
+        // Two passes here - the first is for the call site creation and invoke path, the second
+        // for the lookup and invoke path.
+        for (int pass = 0; pass < 2; ++pass) {
+            testByteReturnValues(); // B
+            testCharReturnValues(); // C
+            testDoubleReturnValues(); // D
+            testFloatReturnValues(); // F
+            testIntReturnValues(); // I
+            testLongReturnValues(); // J
+            testObjectReturnValues(); // L
+            testShortReturnValues(); // S
+            testVoidReturnValues(); // S
+            testBooleanReturnValues(); // Z
+        }
+    }
+}
diff --git a/test/956-methodhandles/expected.txt b/test/956-methodhandles/expected.txt
index 6954c22..a8b609b 100644
--- a/test/956-methodhandles/expected.txt
+++ b/test/956-methodhandles/expected.txt
@@ -15,6 +15,7 @@
 Chatty.chatter()
 Chatty.chatter()
 String constructors done.
+testReturnValues done.
 testReferenceReturnValueConversions done.
 testPrimitiveReturnValueConversions done.
 Hi
diff --git a/test/956-methodhandles/src/Main.java b/test/956-methodhandles/src/Main.java
index dee818a..11d6ead 100644
--- a/test/956-methodhandles/src/Main.java
+++ b/test/956-methodhandles/src/Main.java
@@ -102,6 +102,7 @@
     testAsType();
     testConstructors();
     testStringConstructors();
+    testReturnValues();
     testReturnValueConversions();
     testVariableArity();
     testVariableArity_MethodHandles_bind();
@@ -873,6 +874,89 @@
     System.out.println("String constructors done.");
   }
 
+  private static void testReturnValues() throws Throwable {
+    Lookup lookup = MethodHandles.lookup();
+
+    // byte
+    MethodHandle mhByteValue =
+        lookup.findVirtual(Byte.class, "byteValue", MethodType.methodType(byte.class));
+    assertEquals((byte) -77, (byte) mhByteValue.invokeExact(Byte.valueOf((byte) -77)));
+    assertEquals((byte) -77, (byte) mhByteValue.invoke(Byte.valueOf((byte) -77)));
+
+    // char
+    MethodHandle mhCharacterValue =
+        lookup.findStaticGetter(Character.class, "MAX_SURROGATE", char.class);
+    assertEquals(Character.MAX_SURROGATE, (char) mhCharacterValue.invokeExact());
+    assertEquals(Character.MAX_SURROGATE, (char) mhCharacterValue.invoke());
+
+    // double
+    MethodHandle mhSin =
+        lookup.findStatic(
+            Math.class, "sin", MethodType.methodType(double.class, double.class));
+    for (double i = -Math.PI; i <= Math.PI; i += Math.PI / 8) {
+      assertEquals(Math.sin(i), (double) mhSin.invokeExact(i));
+      assertEquals(Math.sin(i), (double) mhSin.invoke(i));
+    }
+
+    // float
+    MethodHandle mhAbsFloat =
+        lookup.findStatic(
+            Math.class, "abs", MethodType.methodType(float.class, float.class));
+    assertEquals(Math.abs(-3.3e6f), (float) mhAbsFloat.invokeExact(-3.3e6f));
+    assertEquals(Math.abs(-3.3e6f), (float) mhAbsFloat.invoke(-3.3e6f));
+
+    // int
+    MethodHandle mhAbsInt =
+        lookup.findStatic(Math.class, "abs", MethodType.methodType(int.class, int.class));
+    assertEquals(Math.abs(-1000), (int) mhAbsInt.invokeExact(-1000));
+    assertEquals(Math.abs(-1000), (int) mhAbsInt.invoke(-1000));
+
+    // long
+    MethodHandle mhMaxLong =
+        lookup.findStatic(
+            Math.class,
+            "max",
+            MethodType.methodType(long.class, long.class, long.class));
+    assertEquals(
+        Long.MAX_VALUE, (long) mhMaxLong.invokeExact(Long.MAX_VALUE, Long.MAX_VALUE / 2));
+    assertEquals(Long.MAX_VALUE, (long) mhMaxLong.invoke(Long.MAX_VALUE, Long.MAX_VALUE / 2));
+    assertEquals(0x0123456789abcdefL, (long) mhMaxLong.invokeExact(0x0123456789abcdefL, 0L));
+    assertEquals(0x0123456789abcdefL, (long) mhMaxLong.invoke(0x0123456789abcdefL, 0L));
+
+    // ref
+    MethodHandle mhShortValueOf =
+        lookup.findStatic(
+            Short.class, "valueOf", MethodType.methodType(Short.class, short.class));
+    assertEquals(
+        (short) -7890, ((Short) mhShortValueOf.invokeExact((short) -7890)).shortValue());
+    assertEquals((short) -7890, ((Short) mhShortValueOf.invoke((short) -7890)).shortValue());
+
+    // array
+    int [] array = {Integer.MIN_VALUE, -1, 0, +1, Integer.MAX_VALUE};
+    MethodHandle mhCopyOf =
+            lookup.findStatic(
+                Arrays.class, "copyOf", MethodType.methodType(int[].class, int[].class, int.class));
+    assertTrue(Arrays.equals(array, (int[]) mhCopyOf.invokeExact(array, array.length)));
+    assertTrue(Arrays.equals(array, (int[]) mhCopyOf.invoke(array, array.length)));
+
+    // short
+    MethodHandle mhShortValue =
+        lookup.findVirtual(Short.class, "shortValue", MethodType.methodType(short.class));
+    assertEquals((short) 12131, (short) mhShortValue.invokeExact(Short.valueOf((short) 12131)));
+    assertEquals((short) 12131, (short) mhShortValue.invoke(Short.valueOf((short) 12131)));
+
+    // boolean
+    MethodHandle mhBooleanValue =
+        lookup.findVirtual(
+            Boolean.class, "booleanValue", MethodType.methodType(boolean.class));
+    assertEquals(true, (boolean) mhBooleanValue.invokeExact(Boolean.valueOf(true)));
+    assertEquals(true, (boolean) mhBooleanValue.invoke(Boolean.valueOf(true)));
+    assertEquals(false, (boolean) mhBooleanValue.invokeExact(Boolean.valueOf(false)));
+    assertEquals(false, (boolean) mhBooleanValue.invoke(Boolean.valueOf(false)));
+
+    System.out.println("testReturnValues done.");
+  }
+
   private static void testReferenceReturnValueConversions() throws Throwable {
     MethodHandle mh = MethodHandles.lookup().findStatic(
         Float.class, "valueOf", MethodType.methodType(Float.class, String.class));
diff --git a/test/knownfailures.json b/test/knownfailures.json
index ed98d23..c680f53 100644
--- a/test/knownfailures.json
+++ b/test/knownfailures.json
@@ -991,5 +991,11 @@
         "variant": "jit",
         "bug": "b/77567088",
         "description": ["Test throws exception before or during OOME."]
+    },
+    {
+        "tests": ["021-string2"],
+        "variant": "jit & debuggable",
+        "bug": "b/109791792",
+        "description": ["Stack too big."]
     }
 ]
diff --git a/test/testrunner/env.py b/test/testrunner/env.py
index 66ed0d0..1f4b829 100644
--- a/test/testrunner/env.py
+++ b/test/testrunner/env.py
@@ -140,3 +140,6 @@
 
 # include platform prebuilt java, javac, etc in $PATH.
 os.environ['PATH'] = ANDROID_JAVA_TOOLCHAIN + ':' + os.environ['PATH']
+
+DIST_DIR = _get_build_var('DIST_DIR')
+SOONG_OUT_DIR = _get_build_var('SOONG_OUT_DIR')
diff --git a/test/testrunner/testrunner.py b/test/testrunner/testrunner.py
index 2d1398e..044e8dc 100755
--- a/test/testrunner/testrunner.py
+++ b/test/testrunner/testrunner.py
@@ -52,6 +52,7 @@
 import multiprocessing
 import os
 import re
+import shutil
 import subprocess
 import sys
 import tempfile
@@ -1007,6 +1008,9 @@
     build_command += ' -C ' + env.ANDROID_BUILD_TOP
     build_command += ' ' + build_targets
     if subprocess.call(build_command.split()):
+      # Debugging for b/62653020
+      if env.DIST_DIR:
+        shutil.copyfile(env.SOONG_OUT_DIR + '/build.ninja', env.DIST_DIR + '/soong.ninja')
       sys.exit(1)
   if user_requested_tests:
     test_runner_thread = threading.Thread(target=run_tests, args=(user_requested_tests,))
diff --git a/tools/ahat/Android.mk b/tools/ahat/Android.mk
index ad33233..9f423ba 100644
--- a/tools/ahat/Android.mk
+++ b/tools/ahat/Android.mk
@@ -147,6 +147,7 @@
 LOCAL_IS_HOST_MODULE := true
 LOCAL_MODULE_TAGS := tests
 LOCAL_MODULE := ahat-tests
+LOCAL_COMPATIBILITY_SUITE := general-tests
 include $(BUILD_HOST_JAVA_LIBRARY)
 AHAT_TEST_JAR := $(LOCAL_BUILT_MODULE)
 
diff --git a/tools/ahat/AndroidTest.xml b/tools/ahat/AndroidTest.xml
new file mode 100644
index 0000000..867658c
--- /dev/null
+++ b/tools/ahat/AndroidTest.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 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.
+-->
+<configuration description="Runs the ahat unit tests">
+    <option name="null-device" value="true" />
+    <test class="com.android.tradefed.testtype.HostTest" >
+        <option name="class" value="com.android.ahat.AhatTestSuite" />
+    </test>
+</configuration>
diff --git a/tools/ahat/etc/ahat-tests.mf b/tools/ahat/etc/ahat-tests.mf
index af17fad..48fdeb3 100644
--- a/tools/ahat/etc/ahat-tests.mf
+++ b/tools/ahat/etc/ahat-tests.mf
@@ -1 +1 @@
-Main-Class: com.android.ahat.Tests
+Main-Class: com.android.ahat.AhatTestSuite
diff --git a/tools/ahat/src/main/com/android/ahat/Main.java b/tools/ahat/src/main/com/android/ahat/Main.java
index 04a6012..af197d4 100644
--- a/tools/ahat/src/main/com/android/ahat/Main.java
+++ b/tools/ahat/src/main/com/android/ahat/Main.java
@@ -102,7 +102,7 @@
         i++;
         try {
           map.readFromFile(new File(args[i]));
-        } catch (IOException|ParseException ex) {
+        } catch (IOException | ParseException ex) {
           System.out.println("Unable to read proguard map: " + ex);
           System.out.println("The proguard map will not be used.");
         }
@@ -110,7 +110,7 @@
         i++;
         try {
           mapbase.readFromFile(new File(args[i]));
-        } catch (IOException|ParseException ex) {
+        } catch (IOException | ParseException ex) {
           System.out.println("Unable to read baseline proguard map: " + ex);
           System.out.println("The proguard map will not be used.");
         }
diff --git a/tools/ahat/src/test/com/android/ahat/AhatTestSuite.java b/tools/ahat/src/test/com/android/ahat/AhatTestSuite.java
new file mode 100644
index 0000000..bce1f05
--- /dev/null
+++ b/tools/ahat/src/test/com/android/ahat/AhatTestSuite.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ahat;
+
+import org.junit.runner.JUnitCore;
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+
+@RunWith(Suite.class)
+@Suite.SuiteClasses({
+  DiffFieldsTest.class,
+  DiffTest.class,
+  DominatorsTest.class,
+  HtmlEscaperTest.class,
+  InstanceTest.class,
+  NativeAllocationTest.class,
+  ObjectHandlerTest.class,
+  OverviewHandlerTest.class,
+  PerformanceTest.class,
+  ProguardMapTest.class,
+  RootedHandlerTest.class,
+  QueryTest.class,
+  SiteHandlerTest.class,
+  SiteTest.class
+})
+
+public class AhatTestSuite {
+  public static void main(String[] args) {
+    if (args.length == 0) {
+      args = new String[]{"com.android.ahat.AhatTestSuite"};
+    }
+    JUnitCore.main(args);
+  }
+}
diff --git a/tools/ahat/src/test/com/android/ahat/Tests.java b/tools/ahat/src/test/com/android/ahat/Tests.java
deleted file mode 100644
index 0e70432..0000000
--- a/tools/ahat/src/test/com/android/ahat/Tests.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ahat;
-
-import org.junit.runner.JUnitCore;
-
-public class Tests {
-  public static void main(String[] args) {
-    if (args.length == 0) {
-      args = new String[]{
-        "com.android.ahat.DiffFieldsTest",
-        "com.android.ahat.DiffTest",
-        "com.android.ahat.DominatorsTest",
-        "com.android.ahat.HtmlEscaperTest",
-        "com.android.ahat.InstanceTest",
-        "com.android.ahat.NativeAllocationTest",
-        "com.android.ahat.ObjectHandlerTest",
-        "com.android.ahat.OverviewHandlerTest",
-        "com.android.ahat.PerformanceTest",
-        "com.android.ahat.ProguardMapTest",
-        "com.android.ahat.RootedHandlerTest",
-        "com.android.ahat.QueryTest",
-        "com.android.ahat.SiteHandlerTest",
-        "com.android.ahat.SiteTest",
-      };
-    }
-    JUnitCore.main(args);
-  }
-}
-
diff --git a/tools/build/var_list b/tools/build/var_list
index bb005cf..98a5472 100644
--- a/tools/build/var_list
+++ b/tools/build/var_list
@@ -34,3 +34,6 @@
 HOST_OUT_EXECUTABLES
 ANDROID_JAVA_TOOLCHAIN
 
+# b/62653020
+DIST_DIR
+SOONG_OUT_DIR
diff --git a/tools/veridex/Android.mk b/tools/veridex/Android.mk
index f8463c1..2faa577 100644
--- a/tools/veridex/Android.mk
+++ b/tools/veridex/Android.mk
@@ -22,13 +22,13 @@
 system_stub_dex := $(TARGET_OUT_COMMON_INTERMEDIATES)/PACKAGING/core_dex_intermediates/classes.dex
 $(system_stub_dex): PRIVATE_MIN_SDK_VERSION := 1000
 $(system_stub_dex): $(call resolve-prebuilt-sdk-jar-path,system_current) | $(ZIP2ZIP) $(DX)
-	$(transform-classes-d8.jar-to-dex)
+	$(transform-classes.jar-to-dex)
 
 
 oahl_stub_dex := $(TARGET_OUT_COMMON_INTERMEDIATES)/PACKAGING/oahl_dex_intermediates/classes.dex
 $(oahl_stub_dex): PRIVATE_MIN_SDK_VERSION := 1000
 $(oahl_stub_dex): $(call get-prebuilt-sdk-dir,current)/org.apache.http.legacy.jar | $(ZIP2ZIP) $(DX)
-	$(transform-classes-d8.jar-to-dex)
+	$(transform-classes.jar-to-dex)
 
 app_compat_lists := \
   $(INTERNAL_PLATFORM_HIDDENAPI_LIGHT_GREYLIST) \