Basic implementation of invoke / invoke-polymorphic.

Basic switch interpreter support for invoke-polymorphic. This change
allows for virtual/interface and static invokes on method handles.

Support for direct invokes (including constructors) and field
getters and setters will be added in follow up changes.

Bug: 30550796
Test: make test-art-host

Change-Id: Ieb3a991d974060d930d56467908d5c7c11d0e38e
diff --git a/runtime/dex_instruction_list.h b/runtime/dex_instruction_list.h
index e974932..3194c1a 100644
--- a/runtime/dex_instruction_list.h
+++ b/runtime/dex_instruction_list.h
@@ -269,9 +269,8 @@
   V(0xF7, UNUSED_F7, "unused-f7", k10x, kIndexUnknown, 0, kVerifyError) \
   V(0xF8, UNUSED_F8, "unused-f8", k10x, kIndexUnknown, 0, kVerifyError) \
   V(0xF9, UNUSED_F9, "unused-f9", k10x, kIndexUnknown, 0, kVerifyError) \
-  /* TODO(narayan): The following two entries are placeholders. */ \
-  V(0xFA, INVOKE_POLYMORPHIC, "invoke-polymorphic", k45cc, kIndexUnknown, 0, kVerifyError) \
-  V(0xFB, INVOKE_POLYMORPHIC_RANGE, "invoke-polymorphic/range", k4rcc, kIndexUnknown, 0, kVerifyError) \
+  V(0xFA, INVOKE_POLYMORPHIC, "invoke-polymorphic", k45cc, kIndexMethodRef, kContinue | kThrow | kInvoke, kVerifyRegBMethod | kVerifyVarArgNonZero | kExperimental) \
+  V(0xFB, INVOKE_POLYMORPHIC_RANGE, "invoke-polymorphic/range", k4rcc, kIndexMethodRef, kContinue | kThrow | kInvoke, kVerifyRegBMethod | kVerifyVarArgRangeNonZero | kExperimental) \
   V(0xFC, UNUSED_FC, "unused-fc", k10x, kIndexUnknown, 0, kVerifyError) \
   V(0xFD, UNUSED_FD, "unused-fd", k10x, kIndexUnknown, 0, kVerifyError) \
   V(0xFE, UNUSED_FE, "unused-fe", k10x, kIndexUnknown, 0, kVerifyError) \
@@ -301,6 +300,8 @@
   V(k31c) \
   V(k35c) \
   V(k3rc) \
+  V(k45cc) \
+  V(k4rcc) \
   V(k51l)
 
 #endif  // ART_RUNTIME_DEX_INSTRUCTION_LIST_H_
diff --git a/runtime/interpreter/interpreter_common.cc b/runtime/interpreter/interpreter_common.cc
index 7a6162c..2d90734 100644
--- a/runtime/interpreter/interpreter_common.cc
+++ b/runtime/interpreter/interpreter_common.cc
@@ -22,7 +22,13 @@
 #include "debugger.h"
 #include "entrypoints/runtime_asm_entrypoints.h"
 #include "jit/jit.h"
+#include "jvalue.h"
+#include "method_handles.h"
 #include "mirror/array-inl.h"
+#include "mirror/class.h"
+#include "mirror/method_handle_impl.h"
+#include "reflection.h"
+#include "reflection-inl.h"
 #include "stack.h"
 #include "unstarted_runtime.h"
 #include "verifier/method_verifier.h"
@@ -503,8 +509,7 @@
 }
 
 // Separate declaration is required solely for the attributes.
-template <bool is_range,
-          bool do_assignability_check>
+template <bool is_range, bool do_assignability_check>
     REQUIRES_SHARED(Locks::mutator_lock_)
 static inline bool DoCallCommon(ArtMethod* called_method,
                                 Thread* self,
@@ -576,6 +581,130 @@
   }
 }
 
+template<bool is_range, bool do_access_check>
+    REQUIRES_SHARED(Locks::mutator_lock_)
+inline bool DoInvokePolymorphic(Thread* self, ShadowFrame& shadow_frame,
+                                const Instruction* inst, uint16_t inst_data,
+                                JValue* result) {
+  // Invoke-polymorphic instructions always take a receiver. i.e, they are never static.
+  const uint32_t vRegC = (is_range) ? inst->VRegC_4rcc() : inst->VRegC_45cc();
+
+  // The method_idx here is the name of the signature polymorphic method that
+  // was symbolically invoked in bytecode (say MethodHandle.invoke or MethodHandle.invokeExact)
+  // and not the method that we'll dispatch to in the end.
+  //
+  // TODO(narayan) We'll have to check in the verifier that this is in fact a
+  // signature polymorphic method so that we disallow calls via invoke-polymorphic
+  // to non sig-poly methods. This would also have the side effect of verifying
+  // that vRegC really is a reference type.
+  mirror::MethodHandleImpl* const method_handle =
+      reinterpret_cast<mirror::MethodHandleImpl*>(shadow_frame.GetVRegReference(vRegC));
+  if (UNLIKELY(method_handle == nullptr)) {
+    const int method_idx = (is_range) ? inst->VRegB_4rcc() : inst->VRegB_45cc();
+    // Note that the invoke type is kVirtual here because a call to a signature
+    // polymorphic method is shaped like a virtual call at the bytecode level.
+    ThrowNullPointerExceptionForMethodAccess(method_idx, InvokeType::kVirtual);
+
+    result->SetJ(0);
+    return false;
+  }
+
+  // The vRegH value gives the index of the proto_id associated with this
+  // signature polymorphic callsite.
+  const uint32_t callsite_proto_id = (is_range) ? inst->VRegH_4rcc() : inst->VRegH_45cc();
+
+  // Call through to the classlinker and ask it to resolve the static type associated
+  // with the callsite. This information is stored in the dex cache so it's
+  // guaranteed to be fast after the first resolution.
+  StackHandleScope<2> hs(self);
+  ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
+  mirror::Class* caller_class = shadow_frame.GetMethod()->GetDeclaringClass();
+  mirror::MethodType* callsite_type = class_linker->ResolveMethodType(
+      caller_class->GetDexFile(), callsite_proto_id,
+      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(callsite_type == nullptr)) {
+    CHECK(self->IsExceptionPending());
+    result->SetJ(0);
+    return false;
+  }
+
+  const char* old_cause = self->StartAssertNoThreadSuspension("DoInvokePolymorphic");
+
+  // Get the method we're actually invoking along with the kind of
+  // invoke that is desired. We don't need to perform access checks at this
+  // point because they would have been performed on our behalf at the point
+  // of creation of the method handle.
+  ArtMethod* called_method = method_handle->GetTargetMethod();
+  const MethodHandleKind handle_kind = method_handle->GetHandleKind();
+  mirror::MethodType* const handle_type = method_handle->GetMethodType();
+  CHECK(called_method != nullptr);
+  CHECK(handle_type != nullptr);
+
+  // We now have to massage the number of inputs to the target function.
+  // It's always one less than the number of inputs to the signature polymorphic
+  // invoke, the first input being a reference to the MethodHandle itself.
+  const uint16_t number_of_inputs =
+      ((is_range) ? inst->VRegA_4rcc(inst_data) : inst->VRegA_45cc(inst_data)) - 1;
+
+  uint32_t arg[Instruction::kMaxVarArgRegs] = {};
+  uint32_t receiver_vregC = 0;
+  if (is_range) {
+    receiver_vregC = (inst->VRegC_4rcc() + 1);
+  } else {
+    inst->GetVarArgs(arg, inst_data);
+    arg[0] = arg[1];
+    arg[1] = arg[2];
+    arg[2] = arg[3];
+    arg[3] = arg[4];
+    arg[4] = 0;
+    receiver_vregC = arg[0];
+  }
+
+  if (IsInvoke(handle_kind)) {
+    if (handle_kind == kInvokeVirtual || handle_kind == kInvokeInterface) {
+      mirror::Object* receiver = shadow_frame.GetVRegReference(receiver_vregC);
+      mirror::Class* declaring_class = called_method->GetDeclaringClass();
+      // Verify that _vRegC is an object reference and of the type expected by
+      // the receiver.
+      called_method = receiver->GetClass()->FindVirtualMethodForVirtualOrInterface(
+          called_method, kRuntimePointerSize);
+      if (!VerifyObjectIsClass(receiver, declaring_class)) {
+        self->EndAssertNoThreadSuspension(old_cause);
+        return false;
+      }
+    } else if (handle_kind == kInvokeDirect) {
+      // TODO(narayan) : We need to handle the case where the target method is a
+      // constructor here. Also the case where we don't want to dynamically
+      // dispatch based on the type of the receiver.
+      self->EndAssertNoThreadSuspension(old_cause);
+      UNIMPLEMENTED(FATAL) << "Direct invokes are not implemented yet.";
+      return false;
+    }
+
+    // NOTE: handle_kind == kInvokeStatic needs no special treatment here. We
+    // can directly make the call. handle_kind == kInvokeSuper doesn't have any
+    // particular use and can probably be dropped.
+    if (callsite_type->IsExactMatch(handle_type)) {
+      self->EndAssertNoThreadSuspension(old_cause);
+      return DoCallCommon<is_range, do_access_check>(
+          called_method, self, shadow_frame, result, number_of_inputs,
+          arg, receiver_vregC);
+    }
+
+    self->EndAssertNoThreadSuspension(old_cause);
+    UNIMPLEMENTED(FATAL) << "Non exact invokes are not implemented yet.";
+    return false;
+  } else {
+    // TODO(narayan): Implement field getters and setters.
+    self->EndAssertNoThreadSuspension(old_cause);
+    UNIMPLEMENTED(FATAL) << "Field references in method handles are not implemented yet.";
+    return false;
+  }
+}
+
 template <bool is_range,
           bool do_assignability_check>
 static inline bool DoCallCommon(ArtMethod* called_method,
@@ -926,6 +1055,19 @@
 EXPLICIT_DO_CALL_TEMPLATE_DECL(true, true);
 #undef EXPLICIT_DO_CALL_TEMPLATE_DECL
 
+// Explicit DoInvokePolymorphic template function declarations.
+#define EXPLICIT_DO_INVOKE_POLYMORPHIC_TEMPLATE_DECL(_is_range, _do_assignability_check)  \
+  template REQUIRES_SHARED(Locks::mutator_lock_)                                          \
+  bool DoInvokePolymorphic<_is_range, _do_assignability_check>(                           \
+      Thread* self, ShadowFrame& shadow_frame, const Instruction* inst,                   \
+      uint16_t inst_data, JValue* result)
+
+EXPLICIT_DO_INVOKE_POLYMORPHIC_TEMPLATE_DECL(false, false);
+EXPLICIT_DO_INVOKE_POLYMORPHIC_TEMPLATE_DECL(false, true);
+EXPLICIT_DO_INVOKE_POLYMORPHIC_TEMPLATE_DECL(true, false);
+EXPLICIT_DO_INVOKE_POLYMORPHIC_TEMPLATE_DECL(true, true);
+#undef EXPLICIT_DO_INVOKE_POLYMORPHIC_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 0feb013..6b28110 100644
--- a/runtime/interpreter/interpreter_common.h
+++ b/runtime/interpreter/interpreter_common.h
@@ -123,7 +123,7 @@
 bool DoCall(ArtMethod* called_method, Thread* self, ShadowFrame& shadow_frame,
             const Instruction* inst, uint16_t inst_data, JValue* result);
 
-// Handles invoke-XXX/range instructions.
+// Handles all invoke-XXX/range instructions except for invoke-polymorphic[/range].
 // Returns true on success, otherwise throws an exception and returns false.
 template<InvokeType type, bool is_range, bool do_access_check>
 static inline bool DoInvoke(Thread* self, ShadowFrame& shadow_frame, const Instruction* inst,
@@ -164,6 +164,12 @@
   }
 }
 
+// Performs a signature polymorphic invoke (invoke-polymorphic/invoke-polymorphic-range).
+template<bool is_range, bool do_access_check>
+bool DoInvokePolymorphic(Thread* self, ShadowFrame& shadow_frame,
+                         const Instruction* inst, uint16_t inst_data,
+                         JValue* result);
+
 // Handles invoke-virtual-quick and invoke-virtual-quick-range instructions.
 // Returns true on success, otherwise throws an exception and returns false.
 template<bool is_range>
diff --git a/runtime/interpreter/interpreter_switch_impl.cc b/runtime/interpreter/interpreter_switch_impl.cc
index 227130e..6cff1da 100644
--- a/runtime/interpreter/interpreter_switch_impl.cc
+++ b/runtime/interpreter/interpreter_switch_impl.cc
@@ -1542,6 +1542,21 @@
         POSSIBLY_HANDLE_PENDING_EXCEPTION(!success, Next_3xx);
         break;
       }
+      case Instruction::INVOKE_POLYMORPHIC: {
+        PREAMBLE();
+        bool success = DoInvokePolymorphic<false, do_access_check>(
+            self, shadow_frame, inst, inst_data, &result_register);
+        POSSIBLY_HANDLE_PENDING_EXCEPTION(!success, Next_4xx);
+        break;
+      }
+      case Instruction::INVOKE_POLYMORPHIC_RANGE: {
+        PREAMBLE();
+        bool success = DoInvokePolymorphic<true, do_access_check>(
+            self, shadow_frame, inst, inst_data, &result_register);
+        POSSIBLY_HANDLE_PENDING_EXCEPTION(!success, Next_4xx);
+        break;
+        break;
+      }
       case Instruction::NEG_INT:
         PREAMBLE();
         shadow_frame.SetVReg(
@@ -2323,8 +2338,6 @@
       case Instruction::UNUSED_FC ... Instruction::UNUSED_FF:
       case Instruction::UNUSED_79:
       case Instruction::UNUSED_7A:
-      case Instruction::INVOKE_POLYMORPHIC:
-      case Instruction::INVOKE_POLYMORPHIC_RANGE:
         UnexpectedOpcode(inst, shadow_frame);
     }
   } while (!interpret_one_instruction);
diff --git a/runtime/method_handles.h b/runtime/method_handles.h
new file mode 100644
index 0000000..5c68a8f
--- /dev/null
+++ b/runtime/method_handles.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ART_RUNTIME_METHOD_HANDLES_H_
+#define ART_RUNTIME_METHOD_HANDLES_H_
+
+#include <ostream>
+
+namespace art {
+
+// Defines the behaviour of a given method handle. The behaviour
+// of a handle of a given kind is identical to the dex bytecode behaviour
+// of the equivalent instruction.
+//
+// NOTE: These must be kept in sync with the constants defined in
+// java.lang.invoke.MethodHandle.
+enum MethodHandleKind {
+  kInvokeVirtual = 0,
+  kInvokeSuper,
+  kInvokeDirect,
+  kInvokeStatic,
+  kInvokeInterface,
+  kInstanceGet,
+  kInstancePut,
+  kStaticGet,
+  kStaticPut,
+  kLastValidKind = kStaticPut,
+  kLastInvokeKind = kInvokeInterface
+};
+
+// Whether the given method handle kind is some variant of an invoke.
+inline bool IsInvoke(const MethodHandleKind handle_kind) {
+  return handle_kind <= kLastInvokeKind;
+}
+
+}  // namespace art
+
+#endif  // ART_RUNTIME_METHOD_HANDLES_H_
diff --git a/runtime/mirror/method_handle_impl.h b/runtime/mirror/method_handle_impl.h
index a0aae3c..40716ad 100644
--- a/runtime/mirror/method_handle_impl.h
+++ b/runtime/mirror/method_handle_impl.h
@@ -20,6 +20,7 @@
 #include "class.h"
 #include "gc_root.h"
 #include "object.h"
+#include "method_handles.h"
 #include "method_type.h"
 
 namespace art {
@@ -40,6 +41,13 @@
         GetField64(OFFSET_OF_OBJECT_MEMBER(MethodHandle, art_field_or_method_)));
   }
 
+  MethodHandleKind GetHandleKind() REQUIRES_SHARED(Locks::mutator_lock_) {
+    const int32_t handle_kind = GetField32(OFFSET_OF_OBJECT_MEMBER(MethodHandle, handle_kind_));
+
+    DCHECK(handle_kind >= 0 && handle_kind <= MethodHandleKind::kLastValidKind);
+    return static_cast<MethodHandleKind>(handle_kind);
+  }
+
  private:
   HeapReference<mirror::Object> as_type_cache_;
   HeapReference<mirror::MethodType> method_type_;
diff --git a/runtime/verifier/method_verifier.cc b/runtime/verifier/method_verifier.cc
index 50466ed..ae82d6c 100644
--- a/runtime/verifier/method_verifier.cc
+++ b/runtime/verifier/method_verifier.cc
@@ -3040,6 +3040,13 @@
       just_set_result = true;
       break;
     }
+    case Instruction::INVOKE_POLYMORPHIC:
+    case Instruction::INVOKE_POLYMORPHIC_RANGE: {
+      Fail(VERIFY_ERROR_FORCE_INTERPRETER)
+          << "instruction is not supported by verifier; skipping verification";
+      have_pending_experimental_failure_ = true;
+      return false;
+    }
     case Instruction::NEG_INT:
     case Instruction::NOT_INT:
       work_line_->CheckUnaryOp(this, inst, reg_types_.Integer(), reg_types_.Integer());
@@ -3352,8 +3359,6 @@
     case Instruction::UNUSED_FC ... Instruction::UNUSED_FF:
     case Instruction::UNUSED_79:
     case Instruction::UNUSED_7A:
-    case Instruction::INVOKE_POLYMORPHIC:
-    case Instruction::INVOKE_POLYMORPHIC_RANGE:
       Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "Unexpected opcode " << inst->DumpString(dex_file_);
       break;
 
diff --git a/test/955-methodhandles-smali/build b/test/955-methodhandles-smali/build
new file mode 100755
index 0000000..a423ca6
--- /dev/null
+++ b/test/955-methodhandles-smali/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/955-methodhandles-smali/expected.txt b/test/955-methodhandles-smali/expected.txt
new file mode 100644
index 0000000..07d2422
--- /dev/null
+++ b/test/955-methodhandles-smali/expected.txt
@@ -0,0 +1,2 @@
+[String1]+[String2]
+[String1]
diff --git a/test/955-methodhandles-smali/info.txt b/test/955-methodhandles-smali/info.txt
new file mode 100644
index 0000000..d10d3eb
--- /dev/null
+++ b/test/955-methodhandles-smali/info.txt
@@ -0,0 +1,3 @@
+Smali-based tests for method handle invocations.
+
+NOTE: needs to run under ART or a Java 8 Language runtime and compiler.
diff --git a/test/955-methodhandles-smali/run b/test/955-methodhandles-smali/run
new file mode 100755
index 0000000..a9f1822
--- /dev/null
+++ b/test/955-methodhandles-smali/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/955-methodhandles-smali/smali/Main.smali b/test/955-methodhandles-smali/smali/Main.smali
new file mode 100644
index 0000000..2fc92f8
--- /dev/null
+++ b/test/955-methodhandles-smali/smali/Main.smali
@@ -0,0 +1,117 @@
+# 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.
+
+.class LMain;
+.super Ljava/lang/Object;
+
+# MethodHandle Main.getHandleForVirtual(Class<?> defc, String name, MethodType type);
+#
+# Returns a handle to a virtual method on |defc| named name with type |type| using
+# the public lookup object.
+.method public static getHandleForVirtual(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/MethodHandle;
+.registers 5
+
+    # Get a reference to the public lookup object (MethodHandles.publicLookup()).
+    invoke-static {}, Ljava/lang/invoke/MethodHandles;->publicLookup()Ljava/lang/invoke/MethodHandles$Lookup;
+    move-result-object v0
+
+    # Call Lookup.findVirtual(defc, name, type);
+    invoke-virtual {v0, p0, p1, p2}, Ljava/lang/invoke/MethodHandles$Lookup;->findVirtual(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/MethodHandle;
+    move-result-object v1
+    return-object v1
+.end method
+
+# MethodHandle Main.getHandleForStatic(Class<?> defc, String name, MethodType type);
+#
+# Returns a handle to a static method on |defc| named name with type |type| using
+# the public lookup object.
+.method public static getHandleForStatic(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/MethodHandle;
+.registers 5
+
+    # Get a reference to the public lookup object (MethodHandles.publicLookup()).
+    invoke-static {}, Ljava/lang/invoke/MethodHandles;->publicLookup()Ljava/lang/invoke/MethodHandles$Lookup;
+    move-result-object v0
+
+    # Call Lookup.findStatic(defc, name, type);
+    invoke-virtual {v0, p0, p1, p2}, Ljava/lang/invoke/MethodHandles$Lookup;->findStatic(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/MethodHandle;
+    move-result-object v1
+    return-object v1
+.end method
+
+# Returns a method handle to String java.lang.String.concat(String);
+.method public static getStringConcatHandle()Ljava/lang/invoke/MethodHandle;
+.registers 3
+    const-string v0, "concat"
+    invoke-virtual {v0}, Ljava/lang/Object;->getClass()Ljava/lang/Class;
+    move-result-object v1
+
+    # Call MethodType.methodType(rtype=String.class, ptype[0] = String.class)
+    invoke-static {v1, v1}, Ljava/lang/invoke/MethodType;->methodType(Ljava/lang/Class;Ljava/lang/Class;)Ljava/lang/invoke/MethodType;
+    move-result-object v2
+
+    # Call Main.getHandleForVirtual(String.class, "concat", methodType);
+    invoke-static {v1, v0, v2}, LMain;->getHandleForVirtual(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/MethodHandle;
+    move-result-object v0
+    return-object v0
+.end method
+
+# Returns a method handle to static String java.lang.String.valueOf(String);
+.method public static getStringValueOfHandle()Ljava/lang/invoke/MethodHandle;
+.registers 4
+    # set v0 to java.lang.Object.class
+    new-instance v0, Ljava/lang/Object;
+    invoke-direct {v0}, Ljava/lang/Object;-><init>()V
+    invoke-virtual {v0}, Ljava/lang/Object;->getClass()Ljava/lang/Class;
+    move-result-object v0
+
+    # set v1 to the name of the method ("valueOf") and v2 to java.lang.String.class;
+    const-string v1, "valueOf"
+    invoke-virtual {v1}, Ljava/lang/Object;->getClass()Ljava/lang/Class;
+    move-result-object v2
+
+    # Call MethodType.methodType(rtype=String.class, ptype[0]=Object.class)
+    invoke-static {v2, v0}, Ljava/lang/invoke/MethodType;->methodType(Ljava/lang/Class;Ljava/lang/Class;)Ljava/lang/invoke/MethodType;
+    move-result-object v3
+
+    # Call Main.getHandleForStatic(String.class, "valueOf", methodType);
+    invoke-static {v2, v1, v3}, LMain;->getHandleForStatic(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/MethodHandle;
+    move-result-object v0
+    return-object v0
+.end method
+
+
+.method public static main([Ljava/lang/String;)V
+.registers 5
+
+    # Test case 1: Exercise String.concat(String, String) which is a virtual method.
+    invoke-static {}, LMain;->getStringConcatHandle()Ljava/lang/invoke/MethodHandle;
+    move-result-object v0
+    const-string v1, "[String1]"
+    const-string v2, "+[String2]"
+    invoke-polymorphic {v0, v1, v2}, Ljava/lang/invoke/MethodHandle;->invokeExact([Ljava/lang/Object;)Ljava/lang/Object;, (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
+    move-result-object v3
+    sget-object v4, Ljava/lang/System;->out:Ljava/io/PrintStream;
+    invoke-virtual {v4, v3}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
+
+    # Test case 2: Exercise String.valueOf(Object);
+    invoke-static {}, LMain;->getStringValueOfHandle()Ljava/lang/invoke/MethodHandle;
+    move-result-object v0
+    const-string v1, "[String1]"
+    invoke-polymorphic {v0, v1}, Ljava/lang/invoke/MethodHandle;->invokeExact([Ljava/lang/Object;)Ljava/lang/Object;, (Ljava/lang/Object;)Ljava/lang/String;
+    move-result-object v3
+    sget-object v4, Ljava/lang/System;->out:Ljava/io/PrintStream;
+    invoke-virtual {v4, v3}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
+
+    return-void
+.end method
diff --git a/test/etc/default-build b/test/etc/default-build
index 37ce0f2..e663496 100755
--- a/test/etc/default-build
+++ b/test/etc/default-build
@@ -71,13 +71,16 @@
 declare -A JACK_EXPERIMENTAL_ARGS
 JACK_EXPERIMENTAL_ARGS["default-methods"]="-D jack.java.source.version=1.8 -D jack.android.min-api-level=24"
 JACK_EXPERIMENTAL_ARGS["lambdas"]="-D jack.java.source.version=1.8 -D jack.android.min-api-level=24"
+JACK_EXPERIMENTAL_ARGS["method-handles"]="-D jack.java.source.version=1.8 -D jack.android.min-api-level=26"
 
 declare -A SMALI_EXPERIMENTAL_ARGS
 SMALI_EXPERIMENTAL_ARGS["default-methods"]="--api-level 24"
+SMALI_EXPERIMENTAL_ARGS["method-handles"]="--api-level 26"
 
 declare -A JAVAC_EXPERIMENTAL_ARGS
 JAVAC_EXPERIMENTAL_ARGS["default-methods"]="-source 1.8 -target 1.8"
 JAVAC_EXPERIMENTAL_ARGS["lambdas"]="-source 1.8 -target 1.8"
+JAVAC_EXPERIMENTAL_ARGS["method-handles"]="-source 1.8 -target 1.8"
 JAVAC_EXPERIMENTAL_ARGS[${DEFAULT_EXPERIMENT}]="-source 1.7 -target 1.7"
 
 while true; do