ART: Compiler support for invoke-polymorphic.

Adds basic support to invoke method handles in compiled code.

Enables method verification for methods containing invoke-polymorphic.

Adds k45cc/k45rc output to Instruction::DumpString() which
was found to be missing when enabling verification.

Include stack traces in test 957-methodhandle-transforms for
failures so they can be easily identified.

Bug: 30550796,33191393
Test: art/test/run-test 953
Test: m test-art-run-test
Change-Id: Ic9a96ea24906087597d96ad8159a5bc349d06950
diff --git a/compiler/oat_test.cc b/compiler/oat_test.cc
index 4180e0e..86d92ff 100644
--- a/compiler/oat_test.cc
+++ b/compiler/oat_test.cc
@@ -487,7 +487,7 @@
   EXPECT_EQ(72U, sizeof(OatHeader));
   EXPECT_EQ(4U, sizeof(OatMethodOffsets));
   EXPECT_EQ(20U, sizeof(OatQuickMethodHeader));
-  EXPECT_EQ(163 * static_cast<size_t>(GetInstructionSetPointerSize(kRuntimeISA)),
+  EXPECT_EQ(164 * static_cast<size_t>(GetInstructionSetPointerSize(kRuntimeISA)),
             sizeof(QuickEntryPoints));
 }
 
diff --git a/compiler/optimizing/code_generator.cc b/compiler/optimizing/code_generator.cc
index 402eeee..c3e0c0e 100644
--- a/compiler/optimizing/code_generator.cc
+++ b/compiler/optimizing/code_generator.cc
@@ -367,6 +367,12 @@
   InvokeRuntime(entrypoint, invoke, invoke->GetDexPc(), nullptr);
 }
 
+void CodeGenerator::GenerateInvokePolymorphicCall(HInvokePolymorphic* invoke) {
+  MoveConstant(invoke->GetLocations()->GetTemp(0), static_cast<int32_t>(invoke->GetType()));
+  QuickEntrypointEnum entrypoint = kQuickInvokePolymorphic;
+  InvokeRuntime(entrypoint, invoke, invoke->GetDexPc(), nullptr);
+}
+
 void CodeGenerator::CreateUnresolvedFieldLocationSummary(
     HInstruction* field_access,
     Primitive::Type field_type,
diff --git a/compiler/optimizing/code_generator.h b/compiler/optimizing/code_generator.h
index 2e2c3c0..673a661 100644
--- a/compiler/optimizing/code_generator.h
+++ b/compiler/optimizing/code_generator.h
@@ -427,12 +427,12 @@
   }
 
 
-  // Perfoms checks pertaining to an InvokeRuntime call.
+  // Performs checks pertaining to an InvokeRuntime call.
   void ValidateInvokeRuntime(QuickEntrypointEnum entrypoint,
                              HInstruction* instruction,
                              SlowPathCode* slow_path);
 
-  // Perfoms checks pertaining to an InvokeRuntimeWithoutRecordingPcInfo call.
+  // Performs checks pertaining to an InvokeRuntimeWithoutRecordingPcInfo call.
   static void ValidateInvokeRuntimeWithoutRecordingPcInfo(HInstruction* instruction,
                                                           SlowPathCode* slow_path);
 
@@ -496,6 +496,8 @@
 
   void GenerateInvokeUnresolvedRuntimeCall(HInvokeUnresolved* invoke);
 
+  void GenerateInvokePolymorphicCall(HInvokePolymorphic* invoke);
+
   void CreateUnresolvedFieldLocationSummary(
       HInstruction* field_access,
       Primitive::Type field_type,
diff --git a/compiler/optimizing/code_generator_arm.cc b/compiler/optimizing/code_generator_arm.cc
index 1dd526f..61c64a6 100644
--- a/compiler/optimizing/code_generator_arm.cc
+++ b/compiler/optimizing/code_generator_arm.cc
@@ -2370,6 +2370,14 @@
   codegen_->RecordPcInfo(invoke, invoke->GetDexPc());
 }
 
+void LocationsBuilderARM::VisitInvokePolymorphic(HInvokePolymorphic* invoke) {
+  HandleInvoke(invoke);
+}
+
+void InstructionCodeGeneratorARM::VisitInvokePolymorphic(HInvokePolymorphic* invoke) {
+  codegen_->GenerateInvokePolymorphicCall(invoke);
+}
+
 void LocationsBuilderARM::VisitNeg(HNeg* neg) {
   LocationSummary* locations =
       new (GetGraph()->GetArena()) LocationSummary(neg, LocationSummary::kNoCall);
diff --git a/compiler/optimizing/code_generator_arm64.cc b/compiler/optimizing/code_generator_arm64.cc
index 240e39d..4b78ee9 100644
--- a/compiler/optimizing/code_generator_arm64.cc
+++ b/compiler/optimizing/code_generator_arm64.cc
@@ -4080,6 +4080,14 @@
   __ Blr(lr);
 }
 
+void LocationsBuilderARM64::VisitInvokePolymorphic(HInvokePolymorphic* invoke) {
+  HandleInvoke(invoke);
+}
+
+void InstructionCodeGeneratorARM64::VisitInvokePolymorphic(HInvokePolymorphic* invoke) {
+  codegen_->GenerateInvokePolymorphicCall(invoke);
+}
+
 vixl::aarch64::Label* CodeGeneratorARM64::NewPcRelativeStringPatch(
     const DexFile& dex_file,
     uint32_t string_index,
diff --git a/compiler/optimizing/code_generator_arm_vixl.cc b/compiler/optimizing/code_generator_arm_vixl.cc
index cf4d94d..2c764dd 100644
--- a/compiler/optimizing/code_generator_arm_vixl.cc
+++ b/compiler/optimizing/code_generator_arm_vixl.cc
@@ -2445,6 +2445,14 @@
   }
 }
 
+void LocationsBuilderARMVIXL::VisitInvokePolymorphic(HInvokePolymorphic* invoke) {
+  HandleInvoke(invoke);
+}
+
+void InstructionCodeGeneratorARMVIXL::VisitInvokePolymorphic(HInvokePolymorphic* invoke) {
+  codegen_->GenerateInvokePolymorphicCall(invoke);
+}
+
 void LocationsBuilderARMVIXL::VisitNeg(HNeg* neg) {
   LocationSummary* locations =
       new (GetGraph()->GetArena()) LocationSummary(neg, LocationSummary::kNoCall);
diff --git a/compiler/optimizing/code_generator_mips.cc b/compiler/optimizing/code_generator_mips.cc
index 29f8b2a..40c5609 100644
--- a/compiler/optimizing/code_generator_mips.cc
+++ b/compiler/optimizing/code_generator_mips.cc
@@ -5154,6 +5154,14 @@
   }
 }
 
+void LocationsBuilderMIPS::VisitInvokePolymorphic(HInvokePolymorphic* invoke) {
+  HandleInvoke(invoke);
+}
+
+void InstructionCodeGeneratorMIPS::VisitInvokePolymorphic(HInvokePolymorphic* invoke) {
+  codegen_->GenerateInvokePolymorphicCall(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 dd3f0fe..874613f 100644
--- a/compiler/optimizing/code_generator_mips64.cc
+++ b/compiler/optimizing/code_generator_mips64.cc
@@ -3256,6 +3256,14 @@
   HandleInvoke(invoke);
 }
 
+void LocationsBuilderMIPS64::VisitInvokePolymorphic(HInvokePolymorphic* invoke) {
+  HandleInvoke(invoke);
+}
+
+void InstructionCodeGeneratorMIPS64::VisitInvokePolymorphic(HInvokePolymorphic* invoke) {
+  codegen_->GenerateInvokePolymorphicCall(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 786bc50..4752051 100644
--- a/compiler/optimizing/code_generator_x86.cc
+++ b/compiler/optimizing/code_generator_x86.cc
@@ -2244,6 +2244,14 @@
   codegen_->RecordPcInfo(invoke, invoke->GetDexPc());
 }
 
+void LocationsBuilderX86::VisitInvokePolymorphic(HInvokePolymorphic* invoke) {
+  HandleInvoke(invoke);
+}
+
+void InstructionCodeGeneratorX86::VisitInvokePolymorphic(HInvokePolymorphic* invoke) {
+  codegen_->GenerateInvokePolymorphicCall(invoke);
+}
+
 void LocationsBuilderX86::VisitNeg(HNeg* neg) {
   LocationSummary* locations =
       new (GetGraph()->GetArena()) LocationSummary(neg, LocationSummary::kNoCall);
diff --git a/compiler/optimizing/code_generator_x86_64.cc b/compiler/optimizing/code_generator_x86_64.cc
index 06b48c4..550736f 100644
--- a/compiler/optimizing/code_generator_x86_64.cc
+++ b/compiler/optimizing/code_generator_x86_64.cc
@@ -2423,6 +2423,14 @@
   codegen_->RecordPcInfo(invoke, invoke->GetDexPc());
 }
 
+void LocationsBuilderX86_64::VisitInvokePolymorphic(HInvokePolymorphic* invoke) {
+  HandleInvoke(invoke);
+}
+
+void InstructionCodeGeneratorX86_64::VisitInvokePolymorphic(HInvokePolymorphic* invoke) {
+  codegen_->GenerateInvokePolymorphicCall(invoke);
+}
+
 void LocationsBuilderX86_64::VisitNeg(HNeg* neg) {
   LocationSummary* locations =
       new (GetGraph()->GetArena()) LocationSummary(neg, LocationSummary::kNoCall);
diff --git a/compiler/optimizing/graph_visualizer.cc b/compiler/optimizing/graph_visualizer.cc
index 09dcefa..f6fba88 100644
--- a/compiler/optimizing/graph_visualizer.cc
+++ b/compiler/optimizing/graph_visualizer.cc
@@ -464,6 +464,11 @@
     StartAttributeStream("intrinsic") << invoke->GetIntrinsic();
   }
 
+  void VisitInvokePolymorphic(HInvokePolymorphic* invoke) OVERRIDE {
+    VisitInvoke(invoke);
+    StartAttributeStream("invoke_type") << "InvokePolymorphic";
+  }
+
   void VisitInstanceFieldGet(HInstanceFieldGet* iget) OVERRIDE {
     StartAttributeStream("field_name") <<
         iget->GetFieldInfo().GetDexFile().PrettyField(iget->GetFieldInfo().GetFieldIndex(),
diff --git a/compiler/optimizing/inliner.cc b/compiler/optimizing/inliner.cc
index c970e5c..d7da46b 100644
--- a/compiler/optimizing/inliner.cc
+++ b/compiler/optimizing/inliner.cc
@@ -308,8 +308,10 @@
 }
 
 bool HInliner::TryInline(HInvoke* invoke_instruction) {
-  if (invoke_instruction->IsInvokeUnresolved()) {
-    return false;  // Don't bother to move further if we know the method is unresolved.
+  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.
   }
 
   ScopedObjectAccess soa(Thread::Current());
diff --git a/compiler/optimizing/instruction_builder.cc b/compiler/optimizing/instruction_builder.cc
index 009d549..3cfabdd 100644
--- a/compiler/optimizing/instruction_builder.cc
+++ b/compiler/optimizing/instruction_builder.cc
@@ -1,3 +1,4 @@
+
 /*
  * Copyright (C) 2016 The Android Open Source Project
  *
@@ -906,6 +907,33 @@
                       false /* is_unresolved */);
 }
 
+bool HInstructionBuilder::BuildInvokePolymorphic(const Instruction& instruction ATTRIBUTE_UNUSED,
+                                                 uint32_t dex_pc,
+                                                 uint32_t method_idx,
+                                                 uint32_t proto_idx,
+                                                 uint32_t number_of_vreg_arguments,
+                                                 bool is_range,
+                                                 uint32_t* args,
+                                                 uint32_t register_index) {
+  const char* descriptor = dex_file_->GetShorty(proto_idx);
+  DCHECK_EQ(1 + ArtMethod::NumArgRegisters(descriptor), number_of_vreg_arguments);
+  Primitive::Type return_type = Primitive::GetType(descriptor[0]);
+  size_t number_of_arguments = strlen(descriptor);
+  HInvoke* invoke = new (arena_) HInvokePolymorphic(arena_,
+                                                    number_of_arguments,
+                                                    return_type,
+                                                    dex_pc,
+                                                    method_idx);
+  return HandleInvoke(invoke,
+                      number_of_vreg_arguments,
+                      args,
+                      register_index,
+                      is_range,
+                      descriptor,
+                      nullptr /* clinit_check */,
+                      false /* is_unresolved */);
+}
+
 bool HInstructionBuilder::BuildNewInstance(dex::TypeIndex type_index, uint32_t dex_pc) {
   ScopedObjectAccess soa(Thread::Current());
   StackHandleScope<1> hs(soa.Self());
@@ -1915,6 +1943,37 @@
       break;
     }
 
+    case Instruction::INVOKE_POLYMORPHIC: {
+      uint16_t method_idx = instruction.VRegB_45cc();
+      uint16_t proto_idx = instruction.VRegH_45cc();
+      uint32_t number_of_vreg_arguments = instruction.VRegA_45cc();
+      uint32_t args[5];
+      instruction.GetVarArgs(args);
+      return BuildInvokePolymorphic(instruction,
+                                    dex_pc,
+                                    method_idx,
+                                    proto_idx,
+                                    number_of_vreg_arguments,
+                                    false,
+                                    args,
+                                    -1);
+    }
+
+    case Instruction::INVOKE_POLYMORPHIC_RANGE: {
+      uint16_t method_idx = instruction.VRegB_4rcc();
+      uint16_t proto_idx = instruction.VRegH_4rcc();
+      uint32_t number_of_vreg_arguments = instruction.VRegA_4rcc();
+      uint32_t register_index = instruction.VRegC_4rcc();
+      return BuildInvokePolymorphic(instruction,
+                                    dex_pc,
+                                    method_idx,
+                                    proto_idx,
+                                    number_of_vreg_arguments,
+                                    true,
+                                    nullptr,
+                                    register_index);
+    }
+
     case Instruction::NEG_INT: {
       Unop_12x<HNeg>(instruction, Primitive::kPrimInt, dex_pc);
       break;
diff --git a/compiler/optimizing/instruction_builder.h b/compiler/optimizing/instruction_builder.h
index f29e522..aef0b94 100644
--- a/compiler/optimizing/instruction_builder.h
+++ b/compiler/optimizing/instruction_builder.h
@@ -175,6 +175,17 @@
                    uint32_t* args,
                    uint32_t register_index);
 
+  // Builds an invocation node for invoke-polymorphic and returns whether the
+  // instruction is supported.
+  bool BuildInvokePolymorphic(const Instruction& instruction,
+                              uint32_t dex_pc,
+                              uint32_t method_idx,
+                              uint32_t proto_idx,
+                              uint32_t number_of_vreg_arguments,
+                              bool is_range,
+                              uint32_t* args,
+                              uint32_t register_index);
+
   // Builds a new array node and the instructions that fill it.
   void BuildFilledNewArray(uint32_t dex_pc,
                            dex::TypeIndex type_index,
diff --git a/compiler/optimizing/load_store_elimination.cc b/compiler/optimizing/load_store_elimination.cc
index 2856c3e..4f30b11 100644
--- a/compiler/optimizing/load_store_elimination.cc
+++ b/compiler/optimizing/load_store_elimination.cc
@@ -943,6 +943,10 @@
     HandleInvoke(invoke);
   }
 
+  void VisitInvokePolymorphic(HInvokePolymorphic* invoke) OVERRIDE {
+    HandleInvoke(invoke);
+  }
+
   void VisitClinitCheck(HClinitCheck* clinit) OVERRIDE {
     HandleInvoke(clinit);
   }
diff --git a/compiler/optimizing/nodes.h b/compiler/optimizing/nodes.h
index db1b277..2788192 100644
--- a/compiler/optimizing/nodes.h
+++ b/compiler/optimizing/nodes.h
@@ -1291,6 +1291,7 @@
   M(InvokeInterface, Invoke)                                            \
   M(InvokeStaticOrDirect, Invoke)                                       \
   M(InvokeVirtual, Invoke)                                              \
+  M(InvokePolymorphic, Invoke)                                          \
   M(LessThan, Condition)                                                \
   M(LessThanOrEqual, Condition)                                         \
   M(LoadClass, Instruction)                                             \
@@ -3983,6 +3984,28 @@
   DISALLOW_COPY_AND_ASSIGN(HInvokeUnresolved);
 };
 
+class HInvokePolymorphic FINAL : public HInvoke {
+ public:
+  HInvokePolymorphic(ArenaAllocator* arena,
+                     uint32_t number_of_arguments,
+                     Primitive::Type return_type,
+                     uint32_t dex_pc,
+                     uint32_t dex_method_index)
+      : HInvoke(arena,
+                number_of_arguments,
+                0u /* number_of_other_inputs */,
+                return_type,
+                dex_pc,
+                dex_method_index,
+                nullptr,
+                kVirtual) {}
+
+  DECLARE_INSTRUCTION(InvokePolymorphic);
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(HInvokePolymorphic);
+};
+
 class HInvokeStaticOrDirect FINAL : public HInvoke {
  public:
   // Requirements of this method call regarding the class
diff --git a/compiler/utils/assembler_thumb_test_expected.cc.inc b/compiler/utils/assembler_thumb_test_expected.cc.inc
index a3fce02..ab4f9e9 100644
--- a/compiler/utils/assembler_thumb_test_expected.cc.inc
+++ b/compiler/utils/assembler_thumb_test_expected.cc.inc
@@ -5610,7 +5610,7 @@
   " 214:	ecbd 8a10 	vpop	{s16-s31}\n",
   " 218:	e8bd 8de0 	ldmia.w	sp!, {r5, r6, r7, r8, sl, fp, pc}\n",
   " 21c:	4660      	mov	r0, ip\n",
-  " 21e:	f8d9 c2ac 	ldr.w	ip, [r9, #684]	; 0x2ac\n",
+  " 21e:	f8d9 c2b0 	ldr.w	ip, [r9, #688]	; 0x2b0\n",
   " 222:	47e0      	blx	ip\n",
   nullptr
 };
diff --git a/runtime/arch/arm/quick_entrypoints_arm.S b/runtime/arch/arm/quick_entrypoints_arm.S
index 4d4ebdc..9e82f01 100644
--- a/runtime/arch/arm/quick_entrypoints_arm.S
+++ b/runtime/arch/arm/quick_entrypoints_arm.S
@@ -2010,3 +2010,83 @@
 READ_BARRIER_MARK_REG art_quick_read_barrier_mark_reg09, r9
 READ_BARRIER_MARK_REG art_quick_read_barrier_mark_reg10, r10
 READ_BARRIER_MARK_REG art_quick_read_barrier_mark_reg11, r11
+
+.extern artInvokePolymorphic
+ENTRY art_quick_invoke_polymorphic
+    SETUP_SAVE_REFS_AND_ARGS_FRAME r2
+    mov     r2, r9                 @ 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
+    RESTORE_SAVE_REFS_AND_ARGS_FRAME
+    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_long_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
diff --git a/runtime/arch/arm64/quick_entrypoints_arm64.S b/runtime/arch/arm64/quick_entrypoints_arm64.S
index 8b1e038..eba0c87 100644
--- a/runtime/arch/arm64/quick_entrypoints_arm64.S
+++ b/runtime/arch/arm64/quick_entrypoints_arm64.S
@@ -2567,3 +2567,82 @@
 READ_BARRIER_MARK_REG art_quick_read_barrier_mark_reg27, w27, x27
 READ_BARRIER_MARK_REG art_quick_read_barrier_mark_reg28, w28, x28
 READ_BARRIER_MARK_REG art_quick_read_barrier_mark_reg29, w29, x29
+
+.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
+    RESTORE_SAVE_REFS_AND_ARGS_FRAME
+    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)
+    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
+
+END  art_quick_invoke_polymorphic
diff --git a/runtime/arch/x86/quick_entrypoints_x86.S b/runtime/arch/x86/quick_entrypoints_x86.S
index 62c29cf..ea95bbc 100644
--- a/runtime/arch/x86/quick_entrypoints_x86.S
+++ b/runtime/arch/x86/quick_entrypoints_x86.S
@@ -468,7 +468,7 @@
      * The helper will attempt to locate the target and return a 64-bit result in r0/r1 consisting
      * of the target Method* in r0 and method->code_ in r1.
      *
-     * If unsuccessful, the helper will return null/null will bea pending exception in the
+     * If unsuccessful, the helper will return null/null and there will be a pending exception in the
      * thread and we branch to another stub to deliver it.
      *
      * On success this wrapper will restore arguments and *jump* to the target, leaving the lr
@@ -2223,5 +2223,97 @@
     jmp *%ebx
 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()
+    CFI_ADJUST_CFA_OFFSET(4)
+    PUSH ecx                                       // pass receiver (method handle)
+    PUSH eax                                       // pass JResult
+    call SYMBOL(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:
+    add $(.Lhandler_table - .Lbranch_start), %eax  // Add offset of handler table to index.
+    addl %ecx, %eax                                // Add EIP relative address of table.
+    movzbl (%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)
+    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)
+    .byte 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
+
     // TODO: implement these!
 UNIMPLEMENTED art_quick_memcmp16
diff --git a/runtime/arch/x86_64/quick_entrypoints_x86_64.S b/runtime/arch/x86_64/quick_entrypoints_x86_64.S
index facd563..5b3a314 100644
--- a/runtime/arch/x86_64/quick_entrypoints_x86_64.S
+++ b/runtime/arch/x86_64/quick_entrypoints_x86_64.S
@@ -2394,3 +2394,78 @@
     rep movsb                     // while (rcx--) { *rdi++ = *rsi++ }
     jmp *%rdx
 END_FUNCTION art_quick_osr_stub
+
+DEFINE_FUNCTION art_quick_invoke_polymorphic
+    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)
+                                                  // 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
+    movsbq (%rcx, %rax, 1), %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)
+    .byte 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)
+    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
+    RETURN_OR_DELIVER_PENDING_EXCEPTION
+END_FUNCTION art_quick_invoke_polymorphic
diff --git a/runtime/asm_support.h b/runtime/asm_support.h
index bfdddf7..e4972da 100644
--- a/runtime/asm_support.h
+++ b/runtime/asm_support.h
@@ -98,7 +98,7 @@
 ADD_TEST_EQ(THREAD_LOCAL_END_OFFSET,
             art::Thread::ThreadLocalEndOffset<POINTER_SIZE>().Int32Value())
 // Offset of field Thread::tlsPtr_.thread_local_objects.
-#define THREAD_LOCAL_OBJECTS_OFFSET (THREAD_LOCAL_END_OFFSET + __SIZEOF_POINTER__)
+#define THREAD_LOCAL_OBJECTS_OFFSET (THREAD_LOCAL_END_OFFSET + 2 * __SIZEOF_POINTER__)
 ADD_TEST_EQ(THREAD_LOCAL_OBJECTS_OFFSET,
             art::Thread::ThreadLocalObjectsOffset<POINTER_SIZE>().Int32Value())
 // Offset of field Thread::tlsPtr_.mterp_current_ibase.
diff --git a/runtime/common_throws.cc b/runtime/common_throws.cc
index c30272e..a44f79e 100644
--- a/runtime/common_throws.cc
+++ b/runtime/common_throws.cc
@@ -428,6 +428,8 @@
     case Instruction::INVOKE_VIRTUAL_RANGE:
     case Instruction::INVOKE_INTERFACE:
     case Instruction::INVOKE_INTERFACE_RANGE:
+    case Instruction::INVOKE_POLYMORPHIC:
+    case Instruction::INVOKE_POLYMORPHIC_RANGE:
     case Instruction::INVOKE_VIRTUAL_QUICK:
     case Instruction::INVOKE_VIRTUAL_RANGE_QUICK: {
       // Without inlining, we could just check that the offset is the class offset.
@@ -551,6 +553,12 @@
     case Instruction::INVOKE_INTERFACE_RANGE:
       ThrowNullPointerExceptionForMethodAccess(instr->VRegB_3rc(), kInterface);
       break;
+    case Instruction::INVOKE_POLYMORPHIC:
+      ThrowNullPointerExceptionForMethodAccess(instr->VRegB_45cc(), kVirtual);
+      break;
+    case Instruction::INVOKE_POLYMORPHIC_RANGE:
+      ThrowNullPointerExceptionForMethodAccess(instr->VRegB_4rcc(), kVirtual);
+      break;
     case Instruction::INVOKE_VIRTUAL_QUICK:
     case Instruction::INVOKE_VIRTUAL_RANGE_QUICK: {
       // Since we replaced the method index, we ask the verifier to tell us which
diff --git a/runtime/dex_instruction.cc b/runtime/dex_instruction.cc
index 7b8974f..37f3ac9 100644
--- a/runtime/dex_instruction.cc
+++ b/runtime/dex_instruction.cc
@@ -358,7 +358,7 @@
       }
       break;
     case k35c: {
-      uint32_t arg[5];
+      uint32_t arg[kMaxVarArgRegs];
       GetVarArgs(arg);
       switch (Opcode()) {
         case FILLED_NEW_ARRAY:
@@ -443,8 +443,50 @@
       }
       break;
     }
+    case k45cc: {
+      uint32_t arg[kMaxVarArgRegs];
+      GetVarArgs(arg);
+      uint32_t method_idx = VRegB_45cc();
+      uint32_t proto_idx = VRegH_45cc();
+      os << opcode << " {";
+      for (int i = 0; i < VRegA_45cc(); ++i) {
+        if (i != 0) {
+          os << ", ";
+        }
+        os << "v" << arg[i];
+      }
+      os << "}";
+      if (file != nullptr) {
+        os << ", " << file->PrettyMethod(method_idx) << ", " << file->GetShorty(proto_idx)
+           << " // ";
+      } else {
+        os << ", ";
+      }
+      os << "method@" << method_idx << ", proto@" << proto_idx;
+      break;
+    }
+    case k4rcc:
+      switch (Opcode()) {
+        case INVOKE_POLYMORPHIC_RANGE: {
+          if (file != nullptr) {
+            uint32_t method_idx = VRegB_4rcc();
+            uint32_t proto_idx = VRegH_4rcc();
+            os << opcode << ", {v" << VRegC_4rcc() << " .. v" << (VRegC_4rcc() + VRegA_4rcc())
+               << "}, " << file->PrettyMethod(method_idx) << ", " << file->GetShorty(proto_idx)
+               << " // method@" << method_idx << ", proto@" << proto_idx;
+            break;
+          }
+        }
+        FALLTHROUGH_INTENDED;
+        default: {
+          uint32_t method_idx = VRegB_4rcc();
+          uint32_t proto_idx = VRegH_4rcc();
+          os << opcode << ", {v" << VRegC_4rcc() << " .. v" << (VRegC_4rcc() + VRegA_4rcc())
+             << "}, method@" << method_idx << ", proto@" << proto_idx;
+        }
+      }
+      break;
     case k51l: os << StringPrintf("%s v%d, #%+" PRId64, opcode, VRegA_51l(), VRegB_51l()); break;
-    default: os << " unknown format (" << DumpHex(5) << ")"; break;
   }
   return os.str();
 }
diff --git a/runtime/entrypoints/quick/quick_default_externs.h b/runtime/entrypoints/quick/quick_default_externs.h
index 64030f3..2d0932a 100644
--- a/runtime/entrypoints/quick/quick_default_externs.h
+++ b/runtime/entrypoints/quick/quick_default_externs.h
@@ -109,8 +109,13 @@
 extern "C" void art_quick_invoke_interface_trampoline_with_access_check(uint32_t, void*);
 extern "C" void art_quick_invoke_static_trampoline_with_access_check(uint32_t, void*);
 extern "C" void art_quick_invoke_super_trampoline_with_access_check(uint32_t, void*);
+
 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.
+extern "C" void art_quick_invoke_polymorphic(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 78dad94..8ce61c1 100644
--- a/runtime/entrypoints/quick/quick_default_init_entrypoints.h
+++ b/runtime/entrypoints/quick/quick_default_init_entrypoints.h
@@ -106,6 +106,7 @@
       art_quick_invoke_super_trampoline_with_access_check;
   qpoints->pInvokeVirtualTrampolineWithAccessCheck =
       art_quick_invoke_virtual_trampoline_with_access_check;
+  qpoints->pInvokePolymorphic = art_quick_invoke_polymorphic;
 
   // 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 0911aeb..4d5d6de 100644
--- a/runtime/entrypoints/quick/quick_entrypoints_list.h
+++ b/runtime/entrypoints/quick/quick_entrypoints_list.h
@@ -133,6 +133,7 @@
   V(InvokeStaticTrampolineWithAccessCheck, void, uint32_t, void*) \
   V(InvokeSuperTrampolineWithAccessCheck, void, uint32_t, void*) \
   V(InvokeVirtualTrampolineWithAccessCheck, void, uint32_t, void*) \
+  V(InvokePolymorphic, 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 bf1d4ea..bbd4fba 100644
--- a/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc
+++ b/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc
@@ -27,10 +27,12 @@
 #include "imtable-inl.h"
 #include "interpreter/interpreter.h"
 #include "linear_alloc.h"
+#include "method_handles.h"
 #include "method_reference.h"
 #include "mirror/class-inl.h"
 #include "mirror/dex_cache-inl.h"
 #include "mirror/method.h"
+#include "mirror/method_handle_impl.h"
 #include "mirror/object-inl.h"
 #include "mirror/object_array-inl.h"
 #include "oat_quick_method_header.h"
@@ -39,6 +41,7 @@
 #include "scoped_thread_state_change-inl.h"
 #include "stack.h"
 #include "debugger.h"
+#include "well_known_classes.h"
 
 namespace art {
 
@@ -2391,4 +2394,114 @@
                                 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_method_handle,
+    Thread* self,
+    ArtMethod** sp)
+    REQUIRES_SHARED(Locks::mutator_lock_) {
+  ScopedQuickEntrypointChecks sqec(self);
+  DCHECK_EQ(*sp, Runtime::Current()->GetCalleeSaveMethod(Runtime::kSaveRefsAndArgs));
+
+  // 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::CodeItem* code = caller_method->GetCodeItem();
+  const Instruction* inst = Instruction::At(&code->insns_[dex_pc]);
+  DCHECK(inst->Opcode() == Instruction::INVOKE_POLYMORPHIC ||
+         inst->Opcode() == Instruction::INVOKE_POLYMORPHIC_RANGE);
+  const DexFile* dex_file = caller_method->GetDexFile();
+  const uint32_t proto_idx = inst->VRegH();
+  const char* shorty = dex_file->GetShorty(proto_idx);
+  const size_t shorty_length = strlen(shorty);
+  static const bool kMethodIsStatic = false;  // invoke() and invokeExact() are not static.
+  RememberForGcArgumentVisitor gc_visitor(sp, kMethodIsStatic, shorty, shorty_length, &soa);
+  gc_visitor.Visit();
+
+  // Wrap raw_method_handle in a Handle for safety.
+  StackHandleScope<5> hs(self);
+  Handle<mirror::MethodHandleImpl> method_handle(
+      hs.NewHandle(ObjPtr<mirror::MethodHandleImpl>::DownCast(MakeObjPtr(raw_method_handle))));
+  raw_method_handle = nullptr;
+  self->EndAssertNoThreadSuspension(old_cause);
+
+  // Resolve method - it's either MethodHandle.invoke() or MethodHandle.invokeExact().
+  ClassLinker* linker = Runtime::Current()->GetClassLinker();
+  ArtMethod* resolved_method = linker->ResolveMethod<ClassLinker::kForceICCECheck>(self,
+                                                                                   inst->VRegB(),
+                                                                                   caller_method,
+                                                                                   kVirtual);
+  DCHECK((resolved_method ==
+          jni::DecodeArtMethod(WellKnownClasses::java_lang_invoke_MethodHandle_invokeExact)) ||
+         (resolved_method ==
+          jni::DecodeArtMethod(WellKnownClasses::java_lang_invoke_MethodHandle_invoke)));
+  if (UNLIKELY(method_handle.IsNull())) {
+    ThrowNullPointerExceptionForMethodAccess(resolved_method, InvokeType::kVirtual);
+    return static_cast<uintptr_t>('V');
+  }
+
+  Handle<mirror::Class> caller_class(hs.NewHandle(caller_method->GetDeclaringClass()));
+  Handle<mirror::MethodType> method_type(hs.NewHandle(linker->ResolveMethodType(
+      *dex_file, proto_idx,
+      hs.NewHandle<mirror::DexCache>(caller_class->GetDexCache()),
+      hs.NewHandle<mirror::ClassLoader>(caller_class->GetClassLoader()))));
+  // This implies we couldn't resolve one or more types in this method handle.
+  if (UNLIKELY(method_type.IsNull())) {
+    CHECK(self->IsExceptionPending());
+    return static_cast<uintptr_t>('V');
+  }
+
+  DCHECK_EQ(ArtMethod::NumArgRegisters(shorty) + 1u, (uint32_t)inst->VRegA());
+  DCHECK_EQ(resolved_method->IsStatic(), kMethodIsStatic);
+
+  // Fix references before constructing the shadow frame.
+  gc_visitor.FixupReferences();
+
+  // Construct shadow frame placing arguments consecutively from |first_arg|.
+  const bool is_range = (inst->Opcode() == Instruction::INVOKE_POLYMORPHIC_RANGE);
+  const size_t num_vregs = is_range ? inst->VRegA_4rcc() : inst->VRegA_45cc();
+  const size_t first_arg = 0;
+  ShadowFrameAllocaUniquePtr shadow_frame_unique_ptr =
+      CREATE_SHADOW_FRAME(num_vregs, /* link */ nullptr, resolved_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,
+                                                    strlen(shorty),
+                                                    shadow_frame,
+                                                    first_arg);
+  shadow_frame_builder.VisitArguments();
+
+  // Call DoInvokePolymorphic with |is_range| = true, as shadow frame has argument registers in
+  // consecutive order.
+  uint32_t unused_args[Instruction::kMaxVarArgRegs] = {};
+  uint32_t first_callee_arg = first_arg + 1;
+  const bool do_assignability_check = false;
+  if (!DoInvokePolymorphic<true /* is_range */, do_assignability_check>(self,
+                                                                        resolved_method,
+                                                                        *shadow_frame,
+                                                                        method_handle,
+                                                                        method_type,
+                                                                        unused_args,
+                                                                        first_callee_arg,
+                                                                        result)) {
+    DCHECK(self->IsExceptionPending());
+  }
+
+  return static_cast<uintptr_t>(shorty[0]);
+}
+
 }  // namespace art
diff --git a/runtime/entrypoints_order_test.cc b/runtime/entrypoints_order_test.cc
index 6866abb..96e17da 100644
--- a/runtime/entrypoints_order_test.cc
+++ b/runtime/entrypoints_order_test.cc
@@ -121,10 +121,10 @@
                         sizeof(Thread::tls_ptr_sized_values::active_suspend_barriers));
 
     // Skip across the entrypoints structures.
-
-    EXPECT_OFFSET_DIFFP(Thread, tlsPtr_, thread_local_start, thread_local_pos, sizeof(void*));
     EXPECT_OFFSET_DIFFP(Thread, tlsPtr_, thread_local_pos, thread_local_end, sizeof(void*));
-    EXPECT_OFFSET_DIFFP(Thread, tlsPtr_, thread_local_end, thread_local_objects, sizeof(void*));
+    EXPECT_OFFSET_DIFFP(Thread, tlsPtr_, thread_local_end, thread_local_start, sizeof(void*));
+    EXPECT_OFFSET_DIFFP(Thread, tlsPtr_, thread_local_start, thread_local_objects, sizeof(void*));
+
     EXPECT_OFFSET_DIFFP(Thread, tlsPtr_, thread_local_objects, mterp_current_ibase, sizeof(size_t));
     EXPECT_OFFSET_DIFFP(Thread, tlsPtr_, mterp_current_ibase, mterp_default_ibase, sizeof(void*));
     EXPECT_OFFSET_DIFFP(Thread, tlsPtr_, mterp_default_ibase, mterp_alt_ibase, sizeof(void*));
@@ -286,6 +286,8 @@
     EXPECT_OFFSET_DIFFNP(QuickEntryPoints, pInvokeSuperTrampolineWithAccessCheck,
                          pInvokeVirtualTrampolineWithAccessCheck, sizeof(void*));
     EXPECT_OFFSET_DIFFNP(QuickEntryPoints, pInvokeVirtualTrampolineWithAccessCheck,
+                         pInvokePolymorphic, sizeof(void*));
+    EXPECT_OFFSET_DIFFNP(QuickEntryPoints, pInvokePolymorphic,
                          pTestSuspend, sizeof(void*));
     EXPECT_OFFSET_DIFFNP(QuickEntryPoints, pTestSuspend, pDeliverException, sizeof(void*));
 
diff --git a/runtime/oat.h b/runtime/oat.h
index dc103e2..685aa04 100644
--- a/runtime/oat.h
+++ b/runtime/oat.h
@@ -32,7 +32,7 @@
 class PACKED(4) OatHeader {
  public:
   static constexpr uint8_t kOatMagic[] = { 'o', 'a', 't', '\n' };
-  static constexpr uint8_t kOatVersion[] = { '0', '9', '5', '\0' };  // alloc entrypoints change
+  static constexpr uint8_t kOatVersion[] = { '0', '9', '6', '\0' };  // invoke-polymorphic entry
 
   static constexpr const char* kImageLocationKey = "image-location";
   static constexpr const char* kDex2OatCmdLineKey = "dex2oat-cmdline";
diff --git a/runtime/thread.cc b/runtime/thread.cc
index 33c6a40..9ce2e5e 100644
--- a/runtime/thread.cc
+++ b/runtime/thread.cc
@@ -2725,6 +2725,7 @@
   QUICK_ENTRY_POINT_INFO(pInvokeStaticTrampolineWithAccessCheck)
   QUICK_ENTRY_POINT_INFO(pInvokeSuperTrampolineWithAccessCheck)
   QUICK_ENTRY_POINT_INFO(pInvokeVirtualTrampolineWithAccessCheck)
+  QUICK_ENTRY_POINT_INFO(pInvokePolymorphic)
   QUICK_ENTRY_POINT_INFO(pTestSuspend)
   QUICK_ENTRY_POINT_INFO(pDeliverException)
   QUICK_ENTRY_POINT_INFO(pThrowArrayBounds)
diff --git a/runtime/thread.h b/runtime/thread.h
index 6308851..b8c5065 100644
--- a/runtime/thread.h
+++ b/runtime/thread.h
@@ -1416,7 +1416,7 @@
       stacked_shadow_frame_record(nullptr), deoptimization_context_stack(nullptr),
       frame_id_to_shadow_frame(nullptr), name(nullptr), pthread_self(0),
       last_no_thread_suspension_cause(nullptr), checkpoint_function(nullptr),
-      thread_local_start(nullptr), thread_local_pos(nullptr), thread_local_end(nullptr),
+      thread_local_pos(nullptr), thread_local_end(nullptr), thread_local_start(nullptr),
       thread_local_objects(0), mterp_current_ibase(nullptr), mterp_default_ibase(nullptr),
       mterp_alt_ibase(nullptr), thread_local_alloc_stack_top(nullptr),
       thread_local_alloc_stack_end(nullptr), nested_signal_state(nullptr),
@@ -1540,13 +1540,14 @@
     JniEntryPoints jni_entrypoints;
     QuickEntryPoints quick_entrypoints;
 
-    // Thread-local allocation pointer. Moved here to force alignment for thread_local_pos on ARM.
-    uint8_t* thread_local_start;
     // thread_local_pos and thread_local_end must be consecutive for ldrd and are 8 byte aligned for
     // potentially better performance.
     uint8_t* thread_local_pos;
     uint8_t* thread_local_end;
 
+    // Thread-local allocation pointer.
+    uint8_t* thread_local_start;
+
     size_t thread_local_objects;
 
     // Mterp jump table bases.
diff --git a/runtime/verifier/method_verifier.cc b/runtime/verifier/method_verifier.cc
index 715b237..25a179b 100644
--- a/runtime/verifier/method_verifier.cc
+++ b/runtime/verifier/method_verifier.cc
@@ -3106,19 +3106,16 @@
         break;
       }
       const uint32_t proto_idx = (is_range) ? inst->VRegH_4rcc() : inst->VRegH_45cc();
-      const char* descriptor =
+      const char* return_descriptor =
           dex_file_->GetReturnTypeDescriptor(dex_file_->GetProtoId(proto_idx));
       const RegType& return_type =
-          reg_types_.FromDescriptor(GetClassLoader(), descriptor, false);
+          reg_types_.FromDescriptor(GetClassLoader(), return_descriptor, false);
       if (!return_type.IsLowHalf()) {
         work_line_->SetResultRegisterType(this, return_type);
       } else {
         work_line_->SetResultRegisterTypeWide(return_type, return_type.HighHalf(&reg_types_));
       }
-      // TODO(oth): remove when compiler support is available.
-      Fail(VERIFY_ERROR_FORCE_INTERPRETER)
-          << "invoke-polymorphic is not supported by compiler";
-      have_pending_experimental_failure_ = true;
+      just_set_result = true;
       break;
     }
     case Instruction::NEG_INT:
diff --git a/test/953-invoke-polymorphic-compiler/build b/test/953-invoke-polymorphic-compiler/build
new file mode 100755
index 0000000..a423ca6
--- /dev/null
+++ b/test/953-invoke-polymorphic-compiler/build
@@ -0,0 +1,25 @@
+#!/bin/bash
+#
+# Copyright 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# make us exit on a failure
+set -e
+
+if [[ $@ != *"--jvm"* ]]; then
+  # Don't do anything with jvm.
+  export USE_JACK=true
+fi
+
+./default-build "$@" --experimental method-handles
diff --git a/test/953-invoke-polymorphic-compiler/expected.txt b/test/953-invoke-polymorphic-compiler/expected.txt
new file mode 100644
index 0000000..f47ee23
--- /dev/null
+++ b/test/953-invoke-polymorphic-compiler/expected.txt
@@ -0,0 +1,25 @@
+Running Main.Min2Print2([33, -4])
+Running Main.Min2Print2([-4, 33])
+Running Main.Min2Print3([33, -4, 17])
+Running Main.Min2Print3([-4, 17, 33])
+Running Main.Min2Print3([17, 33, -4])
+Running Main.Min2Print6([33, -4, 77, 88, 99, 111])
+Running Main.Min2Print6([-4, 77, 88, 99, 111, 33])
+Running Main.Min2Print6([77, 88, 99, 111, 33, -4])
+Running Main.Min2Print6([88, 99, 111, 33, -4, 77])
+Running Main.Min2Print6([99, 111, 33, -4, 77, 88])
+Running Main.Min2Print6([111, 33, -4, 77, 88, 99])
+Running Main.Min2Print26([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25])
+Running Main.Min2Print26([25, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24])
+Running Main.Min2Print26([24, 25, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23])
+BasicTest done.
+$opt$ReturnBooleanTest done.
+$opt$ReturnCharTest done.
+$opt$ReturnByteTest done.
+$opt$ReturnShortTest done.
+$opt$ReturnIntTest done.
+$opt$ReturnLongTest done.
+$opt$ReturnFloatTest done.
+$opt$ReturnDoubleTest done.
+$opt$ReturnStringTest done.
+ReturnValuesTest done.
diff --git a/test/953-invoke-polymorphic-compiler/info.txt b/test/953-invoke-polymorphic-compiler/info.txt
new file mode 100644
index 0000000..f1dbb61
--- /dev/null
+++ b/test/953-invoke-polymorphic-compiler/info.txt
@@ -0,0 +1,3 @@
+Tests for method handle invocations.
+
+NOTE: needs to run under ART or a Java 8 Language runtime and compiler.
diff --git a/test/953-invoke-polymorphic-compiler/run b/test/953-invoke-polymorphic-compiler/run
new file mode 100755
index 0000000..a9f1822
--- /dev/null
+++ b/test/953-invoke-polymorphic-compiler/run
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# Copyright 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# make us exit on a failure
+set -e
+
+./default-run "$@" --experimental method-handles
diff --git a/test/953-invoke-polymorphic-compiler/src/Main.java b/test/953-invoke-polymorphic-compiler/src/Main.java
new file mode 100644
index 0000000..20a8fec
--- /dev/null
+++ b/test/953-invoke-polymorphic-compiler/src/Main.java
@@ -0,0 +1,374 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodHandles.Lookup;
+import java.lang.invoke.MethodType;
+import java.lang.invoke.WrongMethodTypeException;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+public class Main {
+  public static void assertTrue(boolean value) {
+    if (!value) {
+      throw new AssertionError("assertTrue value: " + value);
+    }
+  }
+
+  public static void assertFalse(boolean value) {
+    if (value) {
+      throw new AssertionError("assertTrue value: " + value);
+    }
+  }
+
+  public static void assertEquals(int i1, int i2) {
+    if (i1 == i2) { return; }
+    throw new AssertionError("assertEquals i1: " + i1 + ", i2: " + i2);
+  }
+
+  public static void assertEquals(long i1, long i2) {
+    if (i1 == i2) { return; }
+    throw new AssertionError("assertEquals l1: " + i1 + ", l2: " + i2);
+  }
+
+  public static void assertEquals(Object o, Object p) {
+    if (o == p) { return; }
+    if (o != null && p != null && o.equals(p)) { return; }
+    throw new AssertionError("assertEquals: o1: " + o + ", o2: " + p);
+  }
+
+  public static void assertEquals(String s1, String s2) {
+    if (s1 == s2) {
+      return;
+    }
+
+    if (s1 != null && s2 != null && s1.equals(s2)) {
+      return;
+    }
+
+    throw new AssertionError("assertEquals s1: " + s1 + ", s2: " + s2);
+  }
+
+  public static void fail() {
+    System.err.println("fail");
+    Thread.dumpStack();
+  }
+
+  public static void fail(String message) {
+    System.err.println("fail: " + message);
+    Thread.dumpStack();
+  }
+
+  public static int Min2Print2(int a, int b) {
+    int[] values = new int[] { a, b };
+    System.err.println("Running Main.Min2Print2(" + Arrays.toString(values) + ")");
+    return a > b ? a : b;
+  }
+
+  public static int Min2Print3(int a, int b, int c) {
+    int[] values = new int[] { a, b, c };
+    System.err.println("Running Main.Min2Print3(" + Arrays.toString(values) + ")");
+    return a > b ? a : b;
+  }
+
+  public static int Min2Print6(int a, int b, int c, int d, int e, int f) {
+    int[] values = new int[] { a, b, c, d, e, f };
+    System.err.println("Running Main.Min2Print6(" + Arrays.toString(values) + ")");
+    return a > b ? a : b;
+  }
+
+  public static int Min2Print26(int a, int b, int c, int d,
+                                int e, int f, int g, int h,
+                                int i, int j, int k, int l,
+                                int m, int n, int o, int p,
+                                int q, int r, int s, int t,
+                                int u, int v, int w, int x,
+                                int y, int z) {
+    int[] values = new int[] { a, b, c, d, e, f, g, h, i, j, k, l, m,
+                               n, o, p, q, r, s, t, u, v, w, x, y, z };
+    System.err.println("Running Main.Min2Print26(" + Arrays.toString(values) + ")");
+    return a > b ? a : b;
+  }
+
+  public static void $opt$BasicTest() throws Throwable {
+    MethodHandle mh;
+    mh = MethodHandles.lookup().findStatic(
+        Main.class, "Min2Print2", MethodType.methodType(int.class, int.class, int.class));
+    assertEquals((int) mh.invokeExact(33, -4), 33);
+    assertEquals((int) mh.invokeExact(-4, 33), 33);
+
+    mh = MethodHandles.lookup().findStatic(
+        Main.class, "Min2Print3",
+        MethodType.methodType(int.class, int.class, int.class, int.class));
+    assertEquals((int) mh.invokeExact(33, -4, 17), 33);
+    assertEquals((int) mh.invokeExact(-4, 17, 33), 17);
+    assertEquals((int) mh.invokeExact(17, 33, -4), 33);
+
+    mh = MethodHandles.lookup().findStatic(
+        Main.class, "Min2Print6",
+        MethodType.methodType(
+            int.class, int.class, int.class, int.class, int.class, int.class, int.class));
+    assertEquals((int) mh.invokeExact(33, -4, 77, 88, 99, 111), 33);
+    try {
+        // Too few arguments
+        assertEquals((int) mh.invokeExact(33, -4, 77, 88), 33);
+        fail("No WMTE for too few arguments");
+    } catch (WrongMethodTypeException e) {}
+    try {
+        // Too many arguments
+        assertEquals((int) mh.invokeExact(33, -4, 77, 88, 89, 90, 91), 33);
+        fail("No WMTE for too many arguments");
+    } catch (WrongMethodTypeException e) {}
+    assertEquals((int) mh.invokeExact(-4, 77, 88, 99, 111, 33), 77);
+    assertEquals((int) mh.invokeExact(77, 88, 99, 111, 33, -4), 88);
+    assertEquals((int) mh.invokeExact(88, 99, 111, 33, -4, 77), 99);
+    assertEquals((int) mh.invokeExact(99, 111, 33, -4, 77, 88), 111);
+    assertEquals((int) mh.invokeExact(111, 33, -4, 77, 88, 99), 111);
+
+    // A preposterous number of arguments.
+    mh = MethodHandles.lookup().findStatic(
+        Main.class, "Min2Print26",
+        MethodType.methodType(
+            // Return-type
+            int.class,
+            // Arguments
+            int.class, int.class, int.class, int.class, int.class, int.class, int.class, int.class,
+            int.class, int.class, int.class, int.class, int.class, int.class, int.class, int.class,
+            int.class, int.class, int.class, int.class, int.class, int.class, int.class, int.class,
+            int.class, int.class));
+    assertEquals(1, (int) mh.invokeExact(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
+                                         13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25));
+    assertEquals(25, (int) mh.invokeExact(25, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
+                                         13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24));
+    assertEquals(25, (int) mh.invokeExact(24, 25, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
+                                         13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23));
+
+    try {
+        // Wrong argument type
+        mh.invokeExact("a");
+        fail("No WMTE for wrong arguments");
+    } catch (WrongMethodTypeException wmte) {}
+
+    try {
+        // Invoke on null handle.
+        MethodHandle mh0 = null;
+        mh0.invokeExact("bad");
+        fail("No NPE for you");
+    } catch (NullPointerException npe) {}
+
+    System.err.println("BasicTest done.");
+  }
+
+  private static boolean And(boolean lhs, boolean rhs) {
+    return lhs & rhs;
+  }
+
+  private static boolean Xor(boolean lhs, boolean rhs) {
+    return lhs ^ rhs;
+  }
+
+  private static String Multiply(String value, int n) {
+    String result = "";
+    for (int i = 0; i < n; ++i) {
+      result = value + result;
+    }
+    return result;
+  }
+
+  private static byte Multiply(byte value, byte n) {
+    return (byte)(value * n);
+  }
+
+  private static short Multiply(short value, short n) {
+    return (short)(value * n);
+  }
+
+  private static int Multiply(int value, int n) {
+    return value * n;
+  }
+
+  private static long Multiply(long value, long n) {
+    return value * n;
+  }
+
+  private static float Multiply(float value, float n) {
+    return value * n;
+  }
+
+  private static double Multiply(double value, double n) {
+    return value * n;
+  }
+
+  private static char Next(char c) {
+    return (char)(c + 1);
+  }
+
+  public static void $opt$ReturnBooleanTest() throws Throwable {
+    MethodHandles.Lookup lookup = MethodHandles.lookup();
+    MethodHandle mh =
+            lookup.findStatic(Main.class, "And",
+                              MethodType.methodType(boolean.class, boolean.class, boolean.class));
+    assertEquals(true, (boolean) mh.invokeExact(true, true));
+    assertEquals(false, (boolean) mh.invokeExact(true, false));
+    assertEquals(false, (boolean) mh.invokeExact(false, true));
+    assertEquals(false, (boolean) mh.invokeExact(false, false));
+    assertEquals(true, (boolean) mh.invoke(true, true));
+    assertEquals(false, (boolean) mh.invoke(true, false));
+    assertEquals(false, (boolean) mh.invoke(false, true));
+    assertEquals(false, (boolean) mh.invoke(false, false));
+
+    mh = lookup.findStatic(Main.class, "Xor",
+                           MethodType.methodType(boolean.class, boolean.class, boolean.class));
+    assertEquals(false, (boolean) mh.invokeExact(true, true));
+    assertEquals(true, (boolean) mh.invokeExact(true, false));
+    assertEquals(true, (boolean) mh.invokeExact(false, true));
+    assertEquals(false, (boolean) mh.invokeExact(false, false));
+    assertEquals(false, (boolean) mh.invoke(true, true));
+    assertEquals(true, (boolean) mh.invoke(true, false));
+    assertEquals(true, (boolean) mh.invoke(false, true));
+    assertEquals(false, (boolean) mh.invoke(false, false));
+
+    System.err.println("$opt$ReturnBooleanTest done.");
+  }
+
+  public static void $opt$ReturnCharTest() throws Throwable {
+    MethodHandles.Lookup lookup = MethodHandles.lookup();
+    MethodHandle mh = lookup.findStatic(Main.class, "Next",
+                           MethodType.methodType(char.class, char.class));
+    assertEquals('B', (char) mh.invokeExact('A'));
+    assertEquals((char) -55, (char) mh.invokeExact((char) -56));
+    System.err.println("$opt$ReturnCharTest done.");
+  }
+
+  public static void $opt$ReturnByteTest() throws Throwable {
+    MethodHandles.Lookup lookup = MethodHandles.lookup();
+    MethodHandle mh = lookup.findStatic(Main.class, "Multiply",
+                                         MethodType.methodType(byte.class, byte.class, byte.class));
+    assertEquals((byte) 30, (byte) mh.invokeExact((byte) 10, (byte) 3));
+    assertEquals((byte) -90, (byte) mh.invoke((byte) -10, (byte) 9));
+    System.err.println("$opt$ReturnByteTest done.");
+  }
+
+  public static void $opt$ReturnShortTest() throws Throwable {
+    MethodHandles.Lookup lookup = MethodHandles.lookup();
+    MethodHandle mh = lookup.findStatic(Main.class, "Multiply",
+                           MethodType.methodType(short.class, short.class, short.class));
+    assertEquals((short) 3000, (short) mh.invokeExact((short) 1000, (short) 3));
+    assertEquals((short) -3000, (short) mh.invoke((short) -1000, (short) 3));
+    System.err.println("$opt$ReturnShortTest done.");
+  }
+
+  public static void $opt$ReturnIntTest() throws Throwable {
+    MethodHandles.Lookup lookup = MethodHandles.lookup();
+    MethodHandle mh = lookup.findStatic(Main.class, "Multiply",
+                           MethodType.methodType(int.class, int.class, int.class));
+    assertEquals(3_000_000, (int) mh.invokeExact(1_000_000, 3));
+    assertEquals(-3_000_000, (int) mh.invoke(-1_000, 3_000));
+    System.err.println("$opt$ReturnIntTest done.");
+  }
+
+  public static void $opt$ReturnLongTest() throws Throwable {
+    MethodHandles.Lookup lookup = MethodHandles.lookup();
+    MethodHandle mh = lookup.findStatic(Main.class, "Multiply",
+                           MethodType.methodType(long.class, long.class, long.class));
+    assertEquals(4_294_967_295_000L, (long) mh.invokeExact(1000L, 4_294_967_295L));
+    assertEquals(-4_294_967_295_000L, (long) mh.invoke(-1000L, 4_294_967_295L));
+    System.err.println("$opt$ReturnLongTest done.");
+  }
+
+  public static void $opt$ReturnFloatTest() throws Throwable {
+    MethodHandles.Lookup lookup = MethodHandles.lookup();
+    MethodHandle mh = lookup.findStatic(Main.class, "Multiply",
+                           MethodType.methodType(float.class, float.class, float.class));
+    assertEquals(3.0F, (float) mh.invokeExact(1000.0F, 3e-3F));
+    assertEquals(-3.0F, (float) mh.invoke(-1000.0F, 3e-3F));
+    System.err.println("$opt$ReturnFloatTest done.");
+  }
+
+  public static void $opt$ReturnDoubleTest() throws Throwable {
+    MethodHandles.Lookup lookup = MethodHandles.lookup();
+    MethodHandle mh = lookup.findStatic(Main.class, "Multiply",
+                           MethodType.methodType(double.class, double.class, double.class));
+    assertEquals(3033000.0, (double) mh.invokeExact(1000.0, 3.033e3));
+    assertEquals(-3033000.0, (double) mh.invoke(-1000.0, 3.033e3));
+    System.err.println("$opt$ReturnDoubleTest done.");
+  }
+
+  public static void $opt$ReturnStringTest() throws Throwable {
+    MethodHandles.Lookup lookup = MethodHandles.lookup();
+    MethodHandle mh = lookup.findStatic(Main.class, "Multiply",
+                           MethodType.methodType(String.class, String.class, int.class));
+    assertEquals("100010001000", (String) mh.invokeExact("1000", 3));
+    assertEquals("100010001000", (String) mh.invoke("1000", 3));
+    System.err.println("$opt$ReturnStringTest done.");
+  }
+
+  public static void ReturnValuesTest() throws Throwable {
+    $opt$ReturnBooleanTest();
+    $opt$ReturnCharTest();
+    $opt$ReturnByteTest();
+    $opt$ReturnShortTest();
+    $opt$ReturnIntTest();
+    $opt$ReturnLongTest();
+    $opt$ReturnFloatTest();
+    $opt$ReturnDoubleTest();
+    $opt$ReturnStringTest();
+    System.err.println("ReturnValuesTest done.");
+  }
+
+  static class ValueHolder {
+    public boolean m_z;
+    public static boolean s_z;
+  }
+
+  public static void $opt$AccessorsTest() throws Throwable {
+    ValueHolder valueHolder = new ValueHolder();
+    MethodHandles.Lookup lookup = MethodHandles.lookup();
+
+    MethodHandle setMember = lookup.findSetter(ValueHolder.class, "m_z", boolean.class);
+    MethodHandle getMember = lookup.findGetter(ValueHolder.class, "m_z", boolean.class);
+    MethodHandle setStatic = lookup.findStaticSetter(ValueHolder.class, "s_z", boolean.class);
+    MethodHandle getStatic = lookup.findStaticGetter(ValueHolder.class, "s_z", boolean.class);
+
+    boolean [] values = { false, true, false, true, false };
+    for (boolean value : values) {
+      assertEquals((boolean) getStatic.invoke(), ValueHolder.s_z);
+      setStatic.invoke(value);
+      ValueHolder.s_z = value;
+      assertEquals(ValueHolder.s_z, value);
+      assertEquals((boolean) getStatic.invoke(), value);
+
+      assertEquals((boolean) getMember.invoke(valueHolder), valueHolder.m_z);
+      setMember.invoke(valueHolder, value);
+      valueHolder.m_z = value;
+      assertEquals(valueHolder.m_z, value);
+      assertEquals((boolean) getMember.invoke(valueHolder), value);
+    }
+  }
+
+  public static void main(String[] args) throws Throwable {
+    $opt$BasicTest();
+    ReturnValuesTest();
+    $opt$AccessorsTest();
+  }
+}
diff --git a/test/957-methodhandle-transforms/src/Main.java b/test/957-methodhandle-transforms/src/Main.java
index 5806509..9e79ff4 100644
--- a/test/957-methodhandle-transforms/src/Main.java
+++ b/test/957-methodhandle-transforms/src/Main.java
@@ -40,17 +40,17 @@
         IllegalArgumentException.class);
 
     if (handle.type().returnType() != String.class) {
-      System.out.println("Unexpected return type for handle: " + handle +
+      fail("Unexpected return type for handle: " + handle +
           " [ " + handle.type() + "]");
     }
 
     final IllegalArgumentException iae = new IllegalArgumentException("boo!");
     try {
       handle.invoke(iae);
-      System.out.println("Expected an exception of type: java.lang.IllegalArgumentException");
+      fail("Expected an exception of type: java.lang.IllegalArgumentException");
     } catch (IllegalArgumentException expected) {
       if (expected != iae) {
-        System.out.println("Wrong exception: expected " + iae + " but was " + expected);
+        fail("Wrong exception: expected " + iae + " but was " + expected);
       }
     }
   }
@@ -262,7 +262,7 @@
       array[0] = 42;
       int value = (int) getter.invoke(array, 0);
       if (value != 42) {
-        System.out.println("Unexpected value: " + value);
+        fail("Unexpected value: " + value);
       }
 
       try {
@@ -284,7 +284,7 @@
       array[0] = 42;
       long value = (long) getter.invoke(array, 0);
       if (value != 42l) {
-        System.out.println("Unexpected value: " + value);
+        fail("Unexpected value: " + value);
       }
     }
 
@@ -294,7 +294,7 @@
       array[0] = 42;
       short value = (short) getter.invoke(array, 0);
       if (value != 42l) {
-        System.out.println("Unexpected value: " + value);
+        fail("Unexpected value: " + value);
       }
     }
 
@@ -304,7 +304,7 @@
       array[0] = 42;
       char value = (char) getter.invoke(array, 0);
       if (value != 42l) {
-        System.out.println("Unexpected value: " + value);
+        fail("Unexpected value: " + value);
       }
     }
 
@@ -314,7 +314,7 @@
       array[0] = (byte) 0x8;
       byte value = (byte) getter.invoke(array, 0);
       if (value != (byte) 0x8) {
-        System.out.println("Unexpected value: " + value);
+        fail("Unexpected value: " + value);
       }
     }
 
@@ -324,7 +324,7 @@
       array[0] = true;
       boolean value = (boolean) getter.invoke(array, 0);
       if (!value) {
-        System.out.println("Unexpected value: " + value);
+        fail("Unexpected value: " + value);
       }
     }
 
@@ -334,7 +334,7 @@
       array[0] = 42.0f;
       float value = (float) getter.invoke(array, 0);
       if (value != 42.0f) {
-        System.out.println("Unexpected value: " + value);
+        fail("Unexpected value: " + value);
       }
     }
 
@@ -344,7 +344,7 @@
       array[0] = 42.0;
       double value = (double) getter.invoke(array, 0);
       if (value != 42.0) {
-        System.out.println("Unexpected value: " + value);
+        fail("Unexpected value: " + value);
       }
     }
 
@@ -372,10 +372,10 @@
       setter.invoke(array, 1, 43);
 
       if (array[0] != 42) {
-        System.out.println("Unexpected value: " + array[0]);
+        fail("Unexpected value: " + array[0]);
       }
       if (array[1] != 43) {
-        System.out.println("Unexpected value: " + array[1]);
+        fail("Unexpected value: " + array[1]);
       }
 
       try {
@@ -396,7 +396,7 @@
       long[] array = new long[1];
       setter.invoke(array, 0, 42l);
       if (array[0] != 42l) {
-        System.out.println("Unexpected value: " + array[0]);
+        fail("Unexpected value: " + array[0]);
       }
     }
 
@@ -405,7 +405,7 @@
       short[] array = new short[1];
       setter.invoke(array, 0, (short) 42);
       if (array[0] != 42l) {
-        System.out.println("Unexpected value: " + array[0]);
+        fail("Unexpected value: " + array[0]);
       }
     }
 
@@ -414,7 +414,7 @@
       char[] array = new char[1];
       setter.invoke(array, 0, (char) 42);
       if (array[0] != 42) {
-        System.out.println("Unexpected value: " + array[0]);
+        fail("Unexpected value: " + array[0]);
       }
     }
 
@@ -423,7 +423,7 @@
       byte[] array = new byte[1];
       setter.invoke(array, 0, (byte) 0x8);
       if (array[0] != (byte) 0x8) {
-        System.out.println("Unexpected value: " + array[0]);
+        fail("Unexpected value: " + array[0]);
       }
     }
 
@@ -432,7 +432,7 @@
       boolean[] array = new boolean[1];
       setter.invoke(array, 0, true);
       if (!array[0]) {
-        System.out.println("Unexpected value: " + array[0]);
+        fail("Unexpected value: " + array[0]);
       }
     }
 
@@ -441,7 +441,7 @@
       float[] array = new float[1];
       setter.invoke(array, 0, 42.0f);
       if (array[0] != 42.0f) {
-        System.out.println("Unexpected value: " + array[0]);
+        fail("Unexpected value: " + array[0]);
       }
     }
 
@@ -450,7 +450,7 @@
       double[] array = new double[1];
       setter.invoke(array, 0, 42.0);
       if (array[0] != 42.0) {
-        System.out.println("Unexpected value: " + array[0]);
+        fail("Unexpected value: " + array[0]);
       }
     }
 
@@ -471,7 +471,7 @@
       MethodHandle identity = MethodHandles.identity(boolean.class);
       boolean value = (boolean) identity.invoke(false);
       if (value) {
-        System.out.println("Unexpected value: " + value);
+        fail("Unexpected value: " + value);
       }
     }
 
@@ -479,7 +479,7 @@
       MethodHandle identity = MethodHandles.identity(byte.class);
       byte value = (byte) identity.invoke((byte) 0x8);
       if (value != (byte) 0x8) {
-        System.out.println("Unexpected value: " + value);
+        fail("Unexpected value: " + value);
       }
     }
 
@@ -487,7 +487,7 @@
       MethodHandle identity = MethodHandles.identity(char.class);
       char value = (char) identity.invoke((char) -56);
       if (value != (char) -56) {
-        System.out.println("Unexpected value: " + value);
+        fail("Unexpected value: " + value);
       }
     }
 
@@ -495,7 +495,7 @@
       MethodHandle identity = MethodHandles.identity(short.class);
       short value = (short) identity.invoke((short) -59);
       if (value != (short) -59) {
-        System.out.println("Unexpected value: " + value);
+        fail("Unexpected value: " + Short.toString(value));
       }
     }
 
@@ -503,7 +503,7 @@
       MethodHandle identity = MethodHandles.identity(int.class);
       int value = (int) identity.invoke(52);
       if (value != 52) {
-        System.out.println("Unexpected value: " + value);
+        fail("Unexpected value: " + value);
       }
     }
 
@@ -511,7 +511,7 @@
       MethodHandle identity = MethodHandles.identity(long.class);
       long value = (long) identity.invoke(-76l);
       if (value != (long) -76) {
-        System.out.println("Unexpected value: " + value);
+        fail("Unexpected value: " + value);
       }
     }
 
@@ -519,7 +519,7 @@
       MethodHandle identity = MethodHandles.identity(float.class);
       float value = (float) identity.invoke(56.0f);
       if (value != (float) 56.0f) {
-        System.out.println("Unexpected value: " + value);
+        fail("Unexpected value: " + value);
       }
     }
 
@@ -527,7 +527,7 @@
       MethodHandle identity = MethodHandles.identity(double.class);
       double value = (double) identity.invoke((double) 72.0);
       if (value != (double) 72.0) {
-        System.out.println("Unexpected value: " + value);
+        fail("Unexpected value: " + value);
       }
     }
 
@@ -544,28 +544,28 @@
       MethodHandle constant = MethodHandles.constant(int.class, 56);
       int value = (int) constant.invoke();
       if (value != 56) {
-        System.out.println("Unexpected value: " + value);
+        fail("Unexpected value: " + value);
       }
 
       // short constant values are converted to int.
       constant = MethodHandles.constant(int.class, (short) 52);
       value = (int) constant.invoke();
       if (value != 52) {
-        System.out.println("Unexpected value: " + value);
+        fail("Unexpected value: " + value);
       }
 
       // char constant values are converted to int.
       constant = MethodHandles.constant(int.class, (char) 'b');
       value = (int) constant.invoke();
       if (value != (int) 'b') {
-        System.out.println("Unexpected value: " + value);
+        fail("Unexpected value: " + value);
       }
 
       // int constant values are converted to int.
       constant = MethodHandles.constant(int.class, (byte) 0x1);
       value = (int) constant.invoke();
       if (value != 1) {
-        System.out.println("Unexpected value: " + value);
+        fail("Unexpected value: " + value);
       }
 
       // boolean, float, double and long primitive constants are not convertible
@@ -600,13 +600,13 @@
       MethodHandle constant = MethodHandles.constant(long.class, 56l);
       long value = (long) constant.invoke();
       if (value != 56l) {
-        System.out.println("Unexpected value: " + value);
+        fail("Unexpected value: " + value);
       }
 
       constant = MethodHandles.constant(long.class, (int) 56);
       value = (long) constant.invoke();
       if (value != 56l) {
-        System.out.println("Unexpected value: " + value);
+        fail("Unexpected value: " + value);
       }
     }
 
@@ -615,7 +615,7 @@
       MethodHandle constant = MethodHandles.constant(byte.class, (byte) 0x12);
       byte value = (byte) constant.invoke();
       if (value != (byte) 0x12) {
-        System.out.println("Unexpected value: " + value);
+        fail("Unexpected value: " + value);
       }
     }
 
@@ -624,7 +624,7 @@
       MethodHandle constant = MethodHandles.constant(boolean.class, true);
       boolean value = (boolean) constant.invoke();
       if (!value) {
-        System.out.println("Unexpected value: " + value);
+        fail("Unexpected value: " + value);
       }
     }
 
@@ -633,7 +633,7 @@
       MethodHandle constant = MethodHandles.constant(char.class, 'f');
       char value = (char) constant.invoke();
       if (value != 'f') {
-        System.out.println("Unexpected value: " + value);
+        fail("Unexpected value: " + value);
       }
     }
 
@@ -642,7 +642,7 @@
       MethodHandle constant = MethodHandles.constant(short.class, (short) 123);
       short value = (short) constant.invoke();
       if (value != (short) 123) {
-        System.out.println("Unexpected value: " + value);
+        fail("Unexpected value: " + value);
       }
     }
 
@@ -651,7 +651,7 @@
       MethodHandle constant = MethodHandles.constant(float.class, 56.0f);
       float value = (float) constant.invoke();
       if (value != 56.0f) {
-        System.out.println("Unexpected value: " + value);
+        fail("Unexpected value: " + value);
       }
     }
 
@@ -660,7 +660,7 @@
       MethodHandle constant = MethodHandles.constant(double.class, 256.0);
       double value = (double) constant.invoke();
       if (value != 256.0) {
-        System.out.println("Unexpected value: " + value);
+        fail("Unexpected value: " + value);
       }
     }
 
@@ -678,13 +678,13 @@
 
     char value = (char) stringCharAt.invoke("foo", 0);
     if (value != 'f') {
-      System.out.println("Unexpected value: " + value);
+      fail("Unexpected value: " + value);
     }
 
     MethodHandle bound = stringCharAt.bindTo("foo");
     value = (char) bound.invoke(0);
     if (value != 'f') {
-      System.out.println("Unexpected value: " + value);
+      fail("Unexpected value: " + value);
     }
 
     try {
@@ -706,7 +706,7 @@
     bound = integerParseInt.bindTo("78452");
     int intValue = (int) bound.invoke();
     if (intValue != 78452) {
-      System.out.println("Unexpected value: " + intValue);
+      fail("Unexpected value: " + intValue);
     }
   }
 
@@ -745,11 +745,11 @@
 
       boolean value = (boolean) adapter.invoke((int) 42);
       if (!value) {
-        System.out.println("Unexpected value: " + value);
+        fail("Unexpected value: " + value);
       }
       value = (boolean) adapter.invoke((int) 43);
       if (value) {
-        System.out.println("Unexpected value: " + value);
+        fail("Unexpected value: " + value);
       }
     }
 
@@ -764,7 +764,7 @@
 
       int value = (int) adapter.invoke("56");
       if (value != 57) {
-        System.out.println("Unexpected value: " + value);
+        fail("Unexpected value: " + value);
       }
     }
 
@@ -779,7 +779,7 @@
 
       int value = (int) adapter.invoke();
       if (value != 42) {
-        System.out.println("Unexpected value: " + value);
+        fail("Unexpected value: " + value);
       }
     }
   }
@@ -791,7 +791,7 @@
       return;
     }
 
-    System.out.println("Unexpected arguments: " + a + ", " + b + ", " + c
+    fail("Unexpected arguments: " + a + ", " + b + ", " + c
         + ", " + d + ", " + e + ", " + f + ", " + g + ", " + h);
   }
 
@@ -800,7 +800,7 @@
       return;
     }
 
-    System.out.println("Unexpected arguments: " + a + ", " + b);
+    fail("Unexpected arguments: " + a + ", " + b);
   }
 
   public static void testPermuteArguments() throws Throwable {
@@ -893,6 +893,11 @@
     Thread.dumpStack();
   }
 
+  public static void fail(String message) {
+    System.out.println("fail: " + message);
+    Thread.dumpStack();
+  }
+
   public static void assertEquals(String s1, String s2) {
     if (s1 == s2) {
       return;