ART: Adds an entrypoint for invoke-custom

Add support for the compiler to call into the runtime for
invoke-custom bytecodes.

Bug: 35337872
Test: art/test.py --host -r -t 952
Test: art/test.py --target --64 -r -t 952
Test: art/test.py --target --32 -r -t 952
Change-Id: I821432e7e5248c91b8e1d36c3112974c34171803
diff --git a/compiler/optimizing/code_generator.cc b/compiler/optimizing/code_generator.cc
index 9f2346d..b3feb78 100644
--- a/compiler/optimizing/code_generator.cc
+++ b/compiler/optimizing/code_generator.cc
@@ -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();
   }
@@ -586,6 +588,12 @@
   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..be8f2b1 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;
   }
 
@@ -2144,14 +2146,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 +2949,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 +2971,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..4ffde7b 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;
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/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/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/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/runtime/arch/arm/quick_entrypoints_arm.S b/runtime/arch/arm/quick_entrypoints_arm.S
index 2ef30c0..ccff9f6 100644
--- a/runtime/arch/arm/quick_entrypoints_arm.S
+++ b/runtime/arch/arm/quick_entrypoints_arm.S
@@ -2693,10 +2693,24 @@
     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.
+    vmov    d0, r0, r1             @ Put result r0:r1 into floating point return register.
     RETURN_OR_DELIVER_PENDING_EXCEPTION_REG r2
 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 5e540dd..80d5fce 100644
--- a/runtime/arch/arm64/quick_entrypoints_arm64.S
+++ b/runtime/arch/arm64/quick_entrypoints_arm64.S
@@ -2855,6 +2855,19 @@
     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 c9bdc96..508a201 100644
--- a/runtime/arch/mips/quick_entrypoints_mips.S
+++ b/runtime/arch/mips/quick_entrypoints_mips.S
@@ -3270,3 +3270,24 @@
 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 1800056..258acdd 100644
--- a/runtime/arch/mips64/quick_entrypoints_mips64.S
+++ b/runtime/arch/mips64/quick_entrypoints_mips64.S
@@ -3070,4 +3070,25 @@
     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 e392198..e1b3df8 100644
--- a/runtime/arch/x86/quick_entrypoints_x86.S
+++ b/runtime/arch/x86/quick_entrypoints_x86.S
@@ -2455,6 +2455,28 @@
     RETURN_OR_DELIVER_PENDING_EXCEPTION
 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 3f5d4f6..9980966 100644
--- a/runtime/arch/x86_64/quick_entrypoints_x86_64.S
+++ b/runtime/arch/x86_64/quick_entrypoints_x86_64.S
@@ -2430,6 +2430,17 @@
     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/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/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 d2b8a98..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"
@@ -2843,4 +2844,62 @@
   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/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/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/verifier/method_verifier.cc b/runtime/verifier/method_verifier.cc
index a62271d..61ddded 100644
--- a/runtime/verifier/method_verifier.cc
+++ b/runtime/verifier/method_verifier.cc
@@ -3053,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.
@@ -3072,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:
@@ -4007,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/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
+        }
+    }
+}