8133299: Nashorn Java adapters should not early bind to functions
authorattila
Sat, 23 Jan 2016 11:50:24 +0100
changeset 35330 7cc832bade3c
parent 35329 069098445d1e
child 35331 cce372c41f8e
8133299: Nashorn Java adapters should not early bind to functions Reviewed-by: hannesw, lagergren, sundar
nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/linker/AdaptationResult.java
nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/linker/Bootstrap.java
nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/linker/JavaAdapterBytecodeGenerator.java
nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/linker/JavaAdapterFactory.java
nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/linker/JavaAdapterServices.java
nashorn/test/src/jdk/nashorn/internal/runtime/linker/test/JavaAdapterTest.java
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/linker/AdaptationResult.java	Fri Jan 22 17:01:41 2016 +0100
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/linker/AdaptationResult.java	Sat Jan 23 11:50:24 2016 +0100
@@ -55,11 +55,17 @@
     static final AdaptationResult SUCCESSFUL_RESULT = new AdaptationResult(Outcome.SUCCESS, "");
 
     private final Outcome outcome;
+    private final RuntimeException cause;
     private final String[] messageArgs;
 
+    AdaptationResult(final Outcome outcome, final RuntimeException cause, final String... messageArgs) {
+        this.outcome = outcome;
+        this.cause = cause;
+        this.messageArgs = messageArgs;
+    }
+
     AdaptationResult(final Outcome outcome, final String... messageArgs) {
-        this.outcome = outcome;
-        this.messageArgs = messageArgs;
+        this(outcome, null, messageArgs);
     }
 
     Outcome getOutcome() {
@@ -67,6 +73,6 @@
     }
 
     ECMAException typeError() {
-        return ECMAErrors.typeError("extend." + outcome, messageArgs);
+        return ECMAErrors.typeError(cause, "extend." + outcome, messageArgs);
     }
 }
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/linker/Bootstrap.java	Fri Jan 22 17:01:41 2016 +0100
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/linker/Bootstrap.java	Sat Jan 23 11:50:24 2016 +0100
@@ -193,7 +193,7 @@
      * Create a call site and link it for Nashorn. This version of the method conforms to the invokedynamic bootstrap
      * method expected signature and is referenced from Nashorn generated bytecode as the bootstrap method for all
      * invokedynamic instructions.
-     * @param lookup MethodHandle lookup. Ignored as Nashorn only uses public lookup.
+     * @param lookup MethodHandle lookup.
      * @param opDesc Dynalink dynamic operation descriptor.
      * @param type   Method type.
      * @param flags  flags for call type, trace/profile etc.
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/linker/JavaAdapterBytecodeGenerator.java	Fri Jan 22 17:01:41 2016 +0100
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/linker/JavaAdapterBytecodeGenerator.java	Sat Jan 23 11:50:24 2016 +0100
@@ -31,19 +31,21 @@
 import static jdk.internal.org.objectweb.asm.Opcodes.ACC_STATIC;
 import static jdk.internal.org.objectweb.asm.Opcodes.ACC_SUPER;
 import static jdk.internal.org.objectweb.asm.Opcodes.ACC_VARARGS;
-import static jdk.internal.org.objectweb.asm.Opcodes.ACONST_NULL;
 import static jdk.internal.org.objectweb.asm.Opcodes.ALOAD;
 import static jdk.internal.org.objectweb.asm.Opcodes.ASTORE;
-import static jdk.internal.org.objectweb.asm.Opcodes.DUP;
-import static jdk.internal.org.objectweb.asm.Opcodes.IFNONNULL;
-import static jdk.internal.org.objectweb.asm.Opcodes.ILOAD;
-import static jdk.internal.org.objectweb.asm.Opcodes.ISTORE;
-import static jdk.internal.org.objectweb.asm.Opcodes.POP;
+import static jdk.internal.org.objectweb.asm.Opcodes.D2F;
+import static jdk.internal.org.objectweb.asm.Opcodes.H_INVOKESTATIC;
+import static jdk.internal.org.objectweb.asm.Opcodes.I2B;
+import static jdk.internal.org.objectweb.asm.Opcodes.I2S;
 import static jdk.internal.org.objectweb.asm.Opcodes.RETURN;
+import static jdk.nashorn.internal.codegen.CompilerConstants.interfaceCallNoLookup;
+import static jdk.nashorn.internal.codegen.CompilerConstants.staticCallNoLookup;
 import static jdk.nashorn.internal.lookup.Lookup.MH;
 import static jdk.nashorn.internal.runtime.linker.AdaptationResult.Outcome.ERROR_NO_ACCESSIBLE_CONSTRUCTOR;
 
+import java.lang.invoke.CallSite;
 import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles.Lookup;
 import java.lang.invoke.MethodType;
 import java.lang.reflect.AccessibleObject;
 import java.lang.reflect.Constructor;
@@ -56,9 +58,7 @@
 import java.util.Collection;
 import java.util.HashSet;
 import java.util.Iterator;
-import java.util.LinkedHashMap;
 import java.util.List;
-import java.util.Map;
 import java.util.Set;
 import jdk.internal.org.objectweb.asm.ClassWriter;
 import jdk.internal.org.objectweb.asm.Handle;
@@ -67,8 +67,7 @@
 import jdk.internal.org.objectweb.asm.Type;
 import jdk.internal.org.objectweb.asm.commons.InstructionAdapter;
 import jdk.nashorn.api.scripting.ScriptUtils;
-import jdk.nashorn.internal.runtime.Context;
-import jdk.nashorn.internal.runtime.JSType;
+import jdk.nashorn.internal.codegen.CompilerConstants.Call;
 import jdk.nashorn.internal.runtime.ScriptFunction;
 import jdk.nashorn.internal.runtime.ScriptObject;
 import jdk.nashorn.internal.runtime.linker.AdaptationResult.Outcome;
@@ -86,8 +85,7 @@
  * dispatched by name. A single JavaScript function will act as the implementation for all overloaded methods of the
  * same name. When methods on an adapter instance are invoked, the functions are invoked having the ScriptObject passed
  * in the instance constructor as their "this". Subsequent changes to the ScriptObject (reassignment or removal of its
- * functions) are not reflected in the adapter instance; the method implementations are bound to functions at
- * constructor invocation time.
+ * functions) will be reflected in the adapter instance as it is live dispatching to its members on every method invocation.
  * {@code java.lang.Object} methods {@code equals}, {@code hashCode}, and {@code toString} can also be overridden. The
  * only restriction is that since every JavaScript object already has a {@code toString} function through the
  * {@code Object.prototype}, the {@code toString} in the adapter is only overridden if the passed ScriptObject has a
@@ -104,7 +102,7 @@
  * strict or not.
  * </li>
  * <li>
- * If the adapter being generated can have class-level overrides, constructors taking same arguments as the superclass
+ * If the adapter being generated has class-level overrides, constructors taking same arguments as the superclass
  * constructors are created. These constructors simply delegate to the superclass constructor. They are simply used to
  * create instances of the adapter class, with no instance-level overrides, as they don't have them. If the original
  * class' constructor was variable arity, the adapter constructor will also be variable arity. Protected constructors
@@ -115,7 +113,7 @@
  * For adapter methods that return values, all the JavaScript-to-Java conversions supported by Nashorn will be in effect
  * to coerce the JavaScript function return value to the expected Java return type.
  * </p><p>
- * Since we are adding a trailing argument to the generated constructors in the adapter class, they will never be
+ * Since we are adding a trailing argument to the generated constructors in the adapter class with instance-level overrides, they will never be
  * declared as variable arity, even if the original constructor in the superclass was declared as variable arity. The
  * reason we are passing the additional argument at the end of the argument list instead at the front is that the
  * source-level script expression <code>new X(a, b) { ... }</code> (which is a proprietary syntax extension Nashorn uses
@@ -137,51 +135,67 @@
  * implemented securely.
  */
 final class JavaAdapterBytecodeGenerator {
-    private static final Type SCRIPTUTILS_TYPE = Type.getType(ScriptUtils.class);
-    private static final Type OBJECT_TYPE = Type.getType(Object.class);
-    private static final Type CLASS_TYPE  = Type.getType(Class.class);
-
-    static final String OBJECT_TYPE_NAME  = OBJECT_TYPE.getInternalName();
-    static final String SCRIPTUTILS_TYPE_NAME  = SCRIPTUTILS_TYPE.getInternalName();
-
-    static final String INIT = "<init>";
+    // Field names in adapters
+    private static final String GLOBAL_FIELD_NAME = "global";
+    private static final String DELEGATE_FIELD_NAME = "delegate";
+    private static final String IS_FUNCTION_FIELD_NAME = "isFunction";
+    private static final String CALL_THIS_FIELD_NAME = "callThis";
 
-    static final String GLOBAL_FIELD_NAME = "global";
+    // Initializer names
+    private static final String INIT = "<init>";
+    private static final String CLASS_INIT = "<clinit>";
 
-    // "global" is declared as Object instead of Global - avoid static references to internal Nashorn classes when possible.
-    static final String GLOBAL_TYPE_DESCRIPTOR = OBJECT_TYPE.getDescriptor();
-
-    static final String SET_GLOBAL_METHOD_DESCRIPTOR = Type.getMethodDescriptor(Type.VOID_TYPE, OBJECT_TYPE);
-    static final String VOID_NOARG_METHOD_DESCRIPTOR = Type.getMethodDescriptor(Type.VOID_TYPE);
-
+    // Types often used in generated bytecode
+    private static final Type OBJECT_TYPE = Type.getType(Object.class);
     private static final Type SCRIPT_OBJECT_TYPE = Type.getType(ScriptObject.class);
     private static final Type SCRIPT_FUNCTION_TYPE = Type.getType(ScriptFunction.class);
-    private static final Type STRING_TYPE = Type.getType(String.class);
-    private static final Type METHOD_TYPE_TYPE = Type.getType(MethodType.class);
-    private static final Type METHOD_HANDLE_TYPE = Type.getType(MethodHandle.class);
-    private static final String GET_HANDLE_OBJECT_DESCRIPTOR = Type.getMethodDescriptor(METHOD_HANDLE_TYPE,
-            OBJECT_TYPE, STRING_TYPE, METHOD_TYPE_TYPE);
-    private static final String GET_HANDLE_FUNCTION_DESCRIPTOR = Type.getMethodDescriptor(METHOD_HANDLE_TYPE,
-            SCRIPT_FUNCTION_TYPE, METHOD_TYPE_TYPE);
-    private static final String GET_CLASS_INITIALIZER_DESCRIPTOR = Type.getMethodDescriptor(OBJECT_TYPE);
-    private static final Type RUNTIME_EXCEPTION_TYPE = Type.getType(RuntimeException.class);
-    private static final Type THROWABLE_TYPE = Type.getType(Throwable.class);
-    private static final Type UNSUPPORTED_OPERATION_TYPE = Type.getType(UnsupportedOperationException.class);
+
+    // JavaAdapterServices methods used in generated bytecode
+    private static final Call CHECK_FUNCTION = lookupServiceMethod("checkFunction", ScriptFunction.class, Object.class, String.class);
+    private static final Call EXPORT_RETURN_VALUE = lookupServiceMethod("exportReturnValue", Object.class, Object.class);
+    private static final Call GET_CALL_THIS = lookupServiceMethod("getCallThis", Object.class, ScriptFunction.class, Object.class);
+    private static final Call GET_CLASS_OVERRIDES = lookupServiceMethod("getClassOverrides", ScriptObject.class);
+    private static final Call GET_NON_NULL_GLOBAL = lookupServiceMethod("getNonNullGlobal", ScriptObject.class);
+    private static final Call HAS_OWN_TO_STRING = lookupServiceMethod("hasOwnToString", boolean.class, ScriptObject.class);
+    private static final Call INVOKE_NO_PERMISSIONS = lookupServiceMethod("invokeNoPermissions", void.class, MethodHandle.class, Object.class);
+    private static final Call NOT_AN_OBJECT = lookupServiceMethod("notAnObject", void.class, Object.class);
+    private static final Call SET_GLOBAL = lookupServiceMethod("setGlobal", Runnable.class, ScriptObject.class);
+    private static final Call TO_CHAR_PRIMITIVE = lookupServiceMethod("toCharPrimitive", char.class, Object.class);
+    private static final Call UNSUPPORTED = lookupServiceMethod("unsupported", UnsupportedOperationException.class);
+    private static final Call WRAP_THROWABLE = lookupServiceMethod("wrapThrowable", RuntimeException.class, Throwable.class);
+
+    // Other methods invoked by the generated bytecode
+    private static final Call UNWRAP = staticCallNoLookup(ScriptUtils.class, "unwrap", Object.class, Object.class);
+    private static final Call CHAR_VALUE_OF = staticCallNoLookup(Character.class, "valueOf", Character.class, char.class);
+    private static final Call DOUBLE_VALUE_OF = staticCallNoLookup(Double.class, "valueOf", Double.class, double.class);
+    private static final Call LONG_VALUE_OF = staticCallNoLookup(Long.class, "valueOf", Long.class, long.class);
+    private static final Call RUN = interfaceCallNoLookup(Runnable.class, "run", void.class);
 
-    private static final String SERVICES_CLASS_TYPE_NAME = Type.getInternalName(JavaAdapterServices.class);
-    private static final String RUNTIME_EXCEPTION_TYPE_NAME = RUNTIME_EXCEPTION_TYPE.getInternalName();
-    private static final String ERROR_TYPE_NAME = Type.getInternalName(Error.class);
-    private static final String THROWABLE_TYPE_NAME = THROWABLE_TYPE.getInternalName();
-    private static final String UNSUPPORTED_OPERATION_TYPE_NAME = UNSUPPORTED_OPERATION_TYPE.getInternalName();
+    // ASM handle to the bootstrap method
+    private static final Handle BOOTSTRAP_HANDLE = new Handle(H_INVOKESTATIC,
+            Type.getInternalName(JavaAdapterServices.class), "bootstrap",
+            MethodType.methodType(CallSite.class, Lookup.class, String.class,
+                    MethodType.class, int.class).toMethodDescriptorString());
+
+    // ASM handle to the bootstrap method for array populator
+    private static final Handle CREATE_ARRAY_BOOTSTRAP_HANDLE = new Handle(H_INVOKESTATIC,
+            Type.getInternalName(JavaAdapterServices.class), "createArrayBootstrap",
+            MethodType.methodType(CallSite.class, Lookup.class, String.class,
+                    MethodType.class).toMethodDescriptorString());
 
-    private static final String METHOD_HANDLE_TYPE_DESCRIPTOR = METHOD_HANDLE_TYPE.getDescriptor();
-    private static final String GET_GLOBAL_METHOD_DESCRIPTOR = Type.getMethodDescriptor(OBJECT_TYPE);
-    private static final String GET_CLASS_METHOD_DESCRIPTOR = Type.getMethodDescriptor(CLASS_TYPE);
-    private static final String EXPORT_RETURN_VALUE_METHOD_DESCRIPTOR = Type.getMethodDescriptor(OBJECT_TYPE, OBJECT_TYPE);
-    private static final String UNWRAP_METHOD_DESCRIPTOR = Type.getMethodDescriptor(OBJECT_TYPE, OBJECT_TYPE);
-    private static final String GET_CONVERTER_METHOD_DESCRIPTOR = Type.getMethodDescriptor(METHOD_HANDLE_TYPE, CLASS_TYPE);
-    private static final String TO_CHAR_PRIMITIVE_METHOD_DESCRIPTOR = Type.getMethodDescriptor(Type.CHAR_TYPE, OBJECT_TYPE);
-    private static final String TO_STRING_METHOD_DESCRIPTOR = Type.getMethodDescriptor(STRING_TYPE, OBJECT_TYPE);
+    // Field type names used in the generated bytecode
+    private static final String SCRIPT_OBJECT_TYPE_DESCRIPTOR = SCRIPT_OBJECT_TYPE.getDescriptor();
+    private static final String OBJECT_TYPE_DESCRIPTOR = OBJECT_TYPE.getDescriptor();
+    private static final String BOOLEAN_TYPE_DESCRIPTOR = Type.BOOLEAN_TYPE.getDescriptor();
+
+    // Throwable names used in the generated bytecode
+    private static final String RUNTIME_EXCEPTION_TYPE_NAME = Type.getInternalName(RuntimeException.class);
+    private static final String ERROR_TYPE_NAME = Type.getInternalName(Error.class);
+    private static final String THROWABLE_TYPE_NAME = Type.getInternalName(Throwable.class);
+
+    // Some more frequently used method descriptors
+    private static final String GET_METHOD_PROPERTY_METHOD_DESCRIPTOR = Type.getMethodDescriptor(OBJECT_TYPE, SCRIPT_OBJECT_TYPE);
+    private static final String VOID_METHOD_DESCRIPTOR = Type.getMethodDescriptor(Type.VOID_TYPE);
 
     // Package used when the adapter can't be defined in the adaptee's package (either because it's sealed, or because
     // it's a java.* package.
@@ -191,11 +205,13 @@
     private static final String JAVA_PACKAGE_PREFIX = "java/";
     private static final int MAX_GENERATED_TYPE_NAME_LENGTH = 255;
 
-    private static final String CLASS_INIT = "<clinit>";
-
     // Method name prefix for invoking super-methods
     static final String SUPER_PREFIX = "super$";
 
+    // Method name and type for the no-privilege finalizer delegate
+    private static final String FINALIZER_DELEGATE_NAME = "$$nashornFinalizerDelegate";
+    private static final String FINALIZER_DELEGATE_METHOD_DESCRIPTOR = Type.getMethodDescriptor(Type.VOID_TYPE, OBJECT_TYPE);
+
     /**
      * Collection of methods we never override: Object.clone(), Object.finalize().
      */
@@ -215,30 +231,13 @@
     private final String superClassName;
     // Binary name of the generated class.
     private final String generatedClassName;
-    private final Set<String> usedFieldNames = new HashSet<>();
     private final Set<String> abstractMethodNames = new HashSet<>();
     private final String samName;
     private final Set<MethodInfo> finalMethods = new HashSet<>(EXCLUDED);
     private final Set<MethodInfo> methodInfos = new HashSet<>();
-    private boolean autoConvertibleFromFunction = false;
+    private final boolean autoConvertibleFromFunction;
     private boolean hasExplicitFinalizer = false;
 
-    /**
-     * Names of static fields holding type converter method handles for return value conversion. We are emitting code
-     * for invoking these explicitly after the delegate handle is invoked, instead of doing an asType or
-     * filterReturnValue on the delegate handle, as that would create a new converter handle wrapping the function's
-     * handle for every instance of the adapter, causing the handle.invokeExact() call sites to become megamorphic.
-     */
-    private final Map<Class<?>, String> converterFields = new LinkedHashMap<>();
-
-    /**
-     * Subset of possible return types for all methods; namely, all possible return types of the SAM methods (we
-     * identify SAM types by having all of their abstract methods share a single name, so there can be multiple
-     * overloads with multiple return types. We use this set when emitting the constructor taking a ScriptFunction (the
-     * SAM initializer) to avoid populating converter fields that will never be used by SAM methods.
-     */
-    private final Set<Class<?>> samReturnTypes = new HashSet<>();
-
     private final ClassWriter cw;
 
     /**
@@ -271,17 +270,22 @@
         generatedClassName = getGeneratedClassName(superClass, interfaces);
 
         cw.visit(Opcodes.V1_7, ACC_PUBLIC | ACC_SUPER, generatedClassName, null, superClassName, getInternalTypeNames(interfaces));
-        generateGlobalFields();
+        generateField(GLOBAL_FIELD_NAME, SCRIPT_OBJECT_TYPE_DESCRIPTOR);
+        generateField(DELEGATE_FIELD_NAME, SCRIPT_OBJECT_TYPE_DESCRIPTOR);
 
         gatherMethods(superClass);
         gatherMethods(interfaces);
-        samName = abstractMethodNames.size() == 1 ? abstractMethodNames.iterator().next() : null;
-        generateHandleFields();
-        generateConverterFields();
+        if (abstractMethodNames.size() == 1) {
+            samName = abstractMethodNames.iterator().next();
+            generateField(CALL_THIS_FIELD_NAME, OBJECT_TYPE_DESCRIPTOR);
+            generateField(IS_FUNCTION_FIELD_NAME, BOOLEAN_TYPE_DESCRIPTOR);
+        } else {
+            samName = null;
+        }
         if(classOverride) {
             generateClassInit();
         }
-        generateConstructors();
+        autoConvertibleFromFunction = generateConstructors();
         generateMethods();
         generateSuperMethods();
         if (hasExplicitFinalizer) {
@@ -291,9 +295,8 @@
         cw.visitEnd();
     }
 
-    private void generateGlobalFields() {
-        cw.visitField(ACC_PRIVATE | ACC_FINAL | (classOverride ? ACC_STATIC : 0), GLOBAL_FIELD_NAME, GLOBAL_TYPE_DESCRIPTOR, null, null).visitEnd();
-        usedFieldNames.add(GLOBAL_FIELD_NAME);
+    private void generateField(final String name, final String fieldDesc) {
+        cw.visitField(ACC_PRIVATE | ACC_FINAL | (classOverride ? ACC_STATIC : 0), name, fieldDesc, null, null).visitEnd();
     }
 
     JavaAdapterClassLoader createAdapterClassLoader() {
@@ -343,154 +346,84 @@
         return interfaceNames;
     }
 
-    private void generateHandleFields() {
-        final int flags = ACC_PRIVATE | ACC_FINAL | (classOverride ? ACC_STATIC : 0);
-        for (final MethodInfo mi: methodInfos) {
-            cw.visitField(flags, mi.methodHandleFieldName, METHOD_HANDLE_TYPE_DESCRIPTOR, null, null).visitEnd();
+    private void generateClassInit() {
+        final InstructionAdapter mv = new InstructionAdapter(cw.visitMethod(ACC_STATIC, CLASS_INIT,
+                VOID_METHOD_DESCRIPTOR, null, null));
+
+        // Assign "global = Context.getGlobal()"
+        GET_NON_NULL_GLOBAL.invoke(mv);
+        mv.putstatic(generatedClassName, GLOBAL_FIELD_NAME, SCRIPT_OBJECT_TYPE_DESCRIPTOR);
+
+        GET_CLASS_OVERRIDES.invoke(mv);
+        if(samName != null) {
+            // If the class is a SAM, allow having ScriptFunction passed as class overrides
+            mv.dup();
+            mv.instanceOf(SCRIPT_FUNCTION_TYPE);
+            mv.dup();
+            mv.putstatic(generatedClassName, IS_FUNCTION_FIELD_NAME, BOOLEAN_TYPE_DESCRIPTOR);
+            final Label notFunction = new Label();
+            mv.ifeq(notFunction);
+            mv.dup();
+            mv.checkcast(SCRIPT_FUNCTION_TYPE);
+            emitInitCallThis(mv);
+            mv.visitLabel(notFunction);
         }
+        mv.putstatic(generatedClassName, DELEGATE_FIELD_NAME, SCRIPT_OBJECT_TYPE_DESCRIPTOR);
+
+        endInitMethod(mv);
     }
 
-    private void generateConverterFields() {
-        final int flags = ACC_PRIVATE | ACC_FINAL | (classOverride ? ACC_STATIC : 0);
-        for (final MethodInfo mi: methodInfos) {
-            final Class<?> returnType = mi.type.returnType();
-            // Handle primitive types, Object, and String specially
-            if(!returnType.isPrimitive() && returnType != Object.class && returnType != String.class) {
-                if(!converterFields.containsKey(returnType)) {
-                    final String name = nextName("convert");
-                    converterFields.put(returnType, name);
-                    if(mi.getName().equals(samName)) {
-                        samReturnTypes.add(returnType);
-                    }
-                    cw.visitField(flags, name, METHOD_HANDLE_TYPE_DESCRIPTOR, null, null).visitEnd();
-                }
-            }
+    /**
+     * Emit bytecode for initializing the "callThis" field.
+     */
+    private void emitInitCallThis(final InstructionAdapter mv) {
+        loadField(mv, GLOBAL_FIELD_NAME, SCRIPT_OBJECT_TYPE_DESCRIPTOR);
+        GET_CALL_THIS.invoke(mv);
+        if(classOverride) {
+            mv.putstatic(generatedClassName, CALL_THIS_FIELD_NAME, OBJECT_TYPE_DESCRIPTOR);
+        } else {
+            // It is presumed ALOAD 0 was already executed
+            mv.putfield(generatedClassName, CALL_THIS_FIELD_NAME, OBJECT_TYPE_DESCRIPTOR);
         }
     }
 
-    private void generateClassInit() {
-        final InstructionAdapter mv = new InstructionAdapter(cw.visitMethod(ACC_STATIC, CLASS_INIT,
-                Type.getMethodDescriptor(Type.VOID_TYPE), null, null));
-
-        mv.invokestatic(SERVICES_CLASS_TYPE_NAME, "getClassOverrides", GET_CLASS_INITIALIZER_DESCRIPTOR, false);
-        final Label initGlobal;
-        if(samName != null) {
-            // If the class is a SAM, allow having a ScriptFunction passed as class overrides
-            final Label notAFunction = new Label();
-            mv.dup();
-            mv.instanceOf(SCRIPT_FUNCTION_TYPE);
-            mv.ifeq(notAFunction);
-            mv.checkcast(SCRIPT_FUNCTION_TYPE);
-
-            // Assign MethodHandle fields through invoking getHandle() for a ScriptFunction, only assigning the SAM
-            // method(s).
-            for (final MethodInfo mi : methodInfos) {
-                if(mi.getName().equals(samName)) {
-                    mv.dup();
-                    loadMethodTypeAndGetHandle(mv, mi, GET_HANDLE_FUNCTION_DESCRIPTOR);
-                } else {
-                    mv.visitInsn(ACONST_NULL);
-                }
-                mv.putstatic(generatedClassName, mi.methodHandleFieldName, METHOD_HANDLE_TYPE_DESCRIPTOR);
-            }
-            initGlobal = new Label();
-            mv.goTo(initGlobal);
-            mv.visitLabel(notAFunction);
-        } else {
-            initGlobal = null;
-        }
-        // Assign MethodHandle fields through invoking getHandle() for a ScriptObject
-        for (final MethodInfo mi : methodInfos) {
-            mv.dup();
-            mv.aconst(mi.getName());
-            loadMethodTypeAndGetHandle(mv, mi, GET_HANDLE_OBJECT_DESCRIPTOR);
-            mv.putstatic(generatedClassName, mi.methodHandleFieldName, METHOD_HANDLE_TYPE_DESCRIPTOR);
-        }
-
-        if(initGlobal != null) {
-            mv.visitLabel(initGlobal);
-        }
-        // Assign "global = Context.getGlobal()"
-        invokeGetGlobalWithNullCheck(mv);
-        mv.putstatic(generatedClassName, GLOBAL_FIELD_NAME, GLOBAL_TYPE_DESCRIPTOR);
-
-        generateConverterInit(mv, false);
-        endInitMethod(mv);
-    }
-
-    private void generateConverterInit(final InstructionAdapter mv, final boolean samOnly) {
-        assert !samOnly || !classOverride;
-        for(final Map.Entry<Class<?>, String> converterField: converterFields.entrySet()) {
-            final Class<?> returnType = converterField.getKey();
-            if(!classOverride) {
-                mv.visitVarInsn(ALOAD, 0);
-            }
-
-            if(samOnly && !samReturnTypes.contains(returnType)) {
-                mv.visitInsn(ACONST_NULL);
-            } else {
-                mv.aconst(Type.getType(converterField.getKey()));
-                mv.invokestatic(SERVICES_CLASS_TYPE_NAME, "getObjectConverter", GET_CONVERTER_METHOD_DESCRIPTOR, false);
-            }
-
-            if(classOverride) {
-                mv.putstatic(generatedClassName, converterField.getValue(), METHOD_HANDLE_TYPE_DESCRIPTOR);
-            } else {
-                mv.putfield(generatedClassName, converterField.getValue(), METHOD_HANDLE_TYPE_DESCRIPTOR);
-            }
-        }
-    }
-
-    private static void loadMethodTypeAndGetHandle(final InstructionAdapter mv, final MethodInfo mi, final String getHandleDescriptor) {
-        // NOTE: we're using generic() here because we'll be linking to the "generic" invoker version of
-        // the functions anyway, so we cut down on megamorphism in the invokeExact() calls in adapter
-        // bodies. Once we start linking to type-specializing invokers, this should be changed.
-        mv.aconst(Type.getMethodType(mi.type.generic().toMethodDescriptorString()));
-        mv.invokestatic(SERVICES_CLASS_TYPE_NAME, "getHandle", getHandleDescriptor, false);
-    }
-
-    private static void invokeGetGlobalWithNullCheck(final InstructionAdapter mv) {
-        invokeGetGlobal(mv);
-        mv.dup();
-        mv.invokevirtual(OBJECT_TYPE_NAME, "getClass", GET_CLASS_METHOD_DESCRIPTOR, false); // check against null Context
-        mv.pop();
-    }
-
-    private void generateConstructors() throws AdaptationException {
+    private boolean generateConstructors() throws AdaptationException {
         boolean gotCtor = false;
+        boolean canBeAutoConverted = false;
         for (final Constructor<?> ctor: superClass.getDeclaredConstructors()) {
             final int modifier = ctor.getModifiers();
             if((modifier & (Modifier.PUBLIC | Modifier.PROTECTED)) != 0 && !isCallerSensitive(ctor)) {
-                generateConstructors(ctor);
+                canBeAutoConverted = generateConstructors(ctor) | canBeAutoConverted;
                 gotCtor = true;
             }
         }
         if(!gotCtor) {
             throw new AdaptationException(ERROR_NO_ACCESSIBLE_CONSTRUCTOR, superClass.getCanonicalName());
         }
+        return canBeAutoConverted;
     }
 
-    private void generateConstructors(final Constructor<?> ctor) {
+    private boolean generateConstructors(final Constructor<?> ctor) {
         if(classOverride) {
             // Generate a constructor that just delegates to ctor. This is used with class-level overrides, when we want
             // to create instances without further per-instance overrides.
             generateDelegatingConstructor(ctor);
-        } else {
-            // Generate a constructor that delegates to ctor, but takes an additional ScriptObject parameter at the
-            // beginning of its parameter list.
-            generateOverridingConstructor(ctor, false);
+            return false;
+        }
+
+        // Generate a constructor that delegates to ctor, but takes an additional ScriptObject parameter at the
+        // beginning of its parameter list.
+        generateOverridingConstructor(ctor, false);
 
-            if (samName != null) {
-                if (!autoConvertibleFromFunction && ctor.getParameterTypes().length == 0) {
-                    // If the original type only has a single abstract method name, as well as a default ctor, then it can
-                    // be automatically converted from JS function.
-                    autoConvertibleFromFunction = true;
-                }
-                // If all our abstract methods have a single name, generate an additional constructor, one that takes a
-                // ScriptFunction as its first parameter and assigns it as the implementation for all abstract methods.
-                generateOverridingConstructor(ctor, true);
-            }
+        if (samName == null) {
+            return false;
         }
+        // If all our abstract methods have a single name, generate an additional constructor, one that takes a
+        // ScriptFunction as its first parameter and assigns it as the implementation for all abstract methods.
+        generateOverridingConstructor(ctor, true);
+        // If the original type only has a single abstract method name, as well as a default ctor, then it can
+        // be automatically converted from JS function.
+        return ctor.getParameterTypes().length == 0;
     }
 
     private void generateDelegatingConstructor(final Constructor<?> ctor) {
@@ -503,14 +436,7 @@
                 Type.getMethodDescriptor(originalCtorType.getReturnType(), argTypes), null, null));
 
         mv.visitCode();
-        // Invoke super constructor with the same arguments.
-        mv.visitVarInsn(ALOAD, 0);
-        int offset = 1; // First arg is at position 1, after this.
-        for (final Type argType: argTypes) {
-            mv.load(offset, argType);
-            offset += argType.getSize();
-        }
-        mv.invokespecial(superClassName, INIT, originalCtorType.getDescriptor(), false);
+        emitSuperConstructorCall(mv, originalCtorType.getDescriptor());
 
         endInitMethod(mv);
     }
@@ -548,80 +474,54 @@
         System.arraycopy(originalArgTypes, 0, newArgTypes, 0, argLen);
 
         // All constructors must be public, even if in the superclass they were protected.
-        // Existing super constructor <init>(this, args...) triggers generating <init>(this, args..., scriptObj).
+        // Existing super constructor <init>(this, args...) triggers generating <init>(this, args..., delegate).
         // Any variable arity constructors become fixed-arity with explicit array arguments.
         final InstructionAdapter mv = new InstructionAdapter(cw.visitMethod(ACC_PUBLIC, INIT,
                 Type.getMethodDescriptor(originalCtorType.getReturnType(), newArgTypes), null, null));
 
         mv.visitCode();
-        // First, invoke super constructor with original arguments. If the form of the constructor we're generating is
-        // <init>(this, args..., scriptFn), then we're invoking super.<init>(this, args...).
-        mv.visitVarInsn(ALOAD, 0);
-        final Class<?>[] argTypes = ctor.getParameterTypes();
-        int offset = 1; // First arg is at position 1, after this.
-        for (int i = 0; i < argLen; ++i) {
-            final Type argType = Type.getType(argTypes[i]);
-            mv.load(offset, argType);
-            offset += argType.getSize();
-        }
-        mv.invokespecial(superClassName, INIT, originalCtorType.getDescriptor(), false);
-
-        // Get a descriptor to the appropriate "JavaAdapterFactory.getHandle" method.
-        final String getHandleDescriptor = fromFunction ? GET_HANDLE_FUNCTION_DESCRIPTOR : GET_HANDLE_OBJECT_DESCRIPTOR;
-
-        // Assign MethodHandle fields through invoking getHandle()
-        for (final MethodInfo mi : methodInfos) {
-            mv.visitVarInsn(ALOAD, 0);
-            if (fromFunction && !mi.getName().equals(samName)) {
-                // Constructors initializing from a ScriptFunction only initialize methods with the SAM name.
-                // NOTE: if there's a concrete overloaded method sharing the SAM name, it'll be overridden too. This
-                // is a deliberate design choice. All other method handles are initialized to null.
-                mv.visitInsn(ACONST_NULL);
-            } else {
-                mv.visitVarInsn(ALOAD, offset);
-                if(!fromFunction) {
-                    mv.aconst(mi.getName());
-                }
-                loadMethodTypeAndGetHandle(mv, mi, getHandleDescriptor);
-            }
-            mv.putfield(generatedClassName, mi.methodHandleFieldName, METHOD_HANDLE_TYPE_DESCRIPTOR);
-        }
+        // First, invoke super constructor with original arguments.
+        final int extraArgOffset = emitSuperConstructorCall(mv, originalCtorType.getDescriptor());
 
         // Assign "this.global = Context.getGlobal()"
         mv.visitVarInsn(ALOAD, 0);
-        invokeGetGlobalWithNullCheck(mv);
-        mv.putfield(generatedClassName, GLOBAL_FIELD_NAME, GLOBAL_TYPE_DESCRIPTOR);
+        GET_NON_NULL_GLOBAL.invoke(mv);
+        mv.putfield(generatedClassName, GLOBAL_FIELD_NAME, SCRIPT_OBJECT_TYPE_DESCRIPTOR);
+
+        // Assign "this.delegate = delegate"
+        mv.visitVarInsn(ALOAD, 0);
+        mv.visitVarInsn(ALOAD, extraArgOffset);
+        mv.putfield(generatedClassName, DELEGATE_FIELD_NAME, SCRIPT_OBJECT_TYPE_DESCRIPTOR);
 
-        // Initialize converters
-        generateConverterInit(mv, fromFunction);
+        if (fromFunction) {
+            // Assign "isFunction = true"
+            mv.visitVarInsn(ALOAD, 0);
+            mv.iconst(1);
+            mv.putfield(generatedClassName, IS_FUNCTION_FIELD_NAME, BOOLEAN_TYPE_DESCRIPTOR);
+
+            mv.visitVarInsn(ALOAD, 0);
+            mv.visitVarInsn(ALOAD, extraArgOffset);
+            emitInitCallThis(mv);
+        }
+
         endInitMethod(mv);
 
         if (! fromFunction) {
             newArgTypes[argLen] = OBJECT_TYPE;
             final InstructionAdapter mv2 = new InstructionAdapter(cw.visitMethod(ACC_PUBLIC, INIT,
                     Type.getMethodDescriptor(originalCtorType.getReturnType(), newArgTypes), null, null));
-            generateOverridingConstructorWithObjectParam(mv2, ctor, originalCtorType.getDescriptor());
+            generateOverridingConstructorWithObjectParam(mv2, originalCtorType.getDescriptor());
         }
     }
 
     // Object additional param accepting constructor - generated to handle null and undefined value
     // for script adapters. This is effectively to throw TypeError on such script adapters. See
     // JavaAdapterServices.getHandle as well.
-    private void generateOverridingConstructorWithObjectParam(final InstructionAdapter mv, final Constructor<?> ctor, final String ctorDescriptor) {
+    private void generateOverridingConstructorWithObjectParam(final InstructionAdapter mv, final String ctorDescriptor) {
         mv.visitCode();
-        mv.visitVarInsn(ALOAD, 0);
-        final Class<?>[] argTypes = ctor.getParameterTypes();
-        int offset = 1; // First arg is at position 1, after this.
-        for (int i = 0; i < argTypes.length; ++i) {
-            final Type argType = Type.getType(argTypes[i]);
-            mv.load(offset, argType);
-            offset += argType.getSize();
-        }
-        mv.invokespecial(superClassName, INIT, ctorDescriptor, false);
-        mv.visitVarInsn(ALOAD, offset);
-        mv.visitInsn(ACONST_NULL);
-        mv.visitInsn(ACONST_NULL);
-        mv.invokestatic(SERVICES_CLASS_TYPE_NAME, "getHandle", GET_HANDLE_OBJECT_DESCRIPTOR, false);
+        final int extraArgOffset = emitSuperConstructorCall(mv, ctorDescriptor);
+        mv.visitVarInsn(ALOAD, extraArgOffset);
+        NOT_AN_OBJECT.invoke(mv);
         endInitMethod(mv);
     }
 
@@ -635,14 +535,6 @@
         mv.visitEnd();
     }
 
-    private static void invokeGetGlobal(final InstructionAdapter mv) {
-        mv.invokestatic(SERVICES_CLASS_TYPE_NAME, "getGlobal", GET_GLOBAL_METHOD_DESCRIPTOR, false);
-    }
-
-    private static void invokeSetGlobal(final InstructionAdapter mv) {
-        mv.invokestatic(SERVICES_CLASS_TYPE_NAME, "setGlobal", SET_GLOBAL_METHOD_DESCRIPTOR, false);
-    }
-
     /**
      * Encapsulation of the information used to generate methods in the adapter classes. Basically, a wrapper around the
      * reflective Method object, a cached MethodType, and the name of the field in the adapter class that will hold the
@@ -652,7 +544,6 @@
     private static class MethodInfo {
         private final Method method;
         private final MethodType type;
-        private String methodHandleFieldName;
 
         private MethodInfo(final Class<?> clazz, final String name, final Class<?>... argTypes) throws NoSuchMethodException {
             this(clazz.getDeclaredMethod(name, argTypes));
@@ -681,21 +572,6 @@
         public int hashCode() {
             return getName().hashCode() ^ type.hashCode();
         }
-
-        void setIsCanonical(final JavaAdapterBytecodeGenerator self) {
-            methodHandleFieldName = self.nextName(getName());
-        }
-    }
-
-    private String nextName(final String name) {
-        int i = 0;
-        String nextName = name;
-        while (!usedFieldNames.add(nextName)) {
-            final String ordinal = String.valueOf(i++);
-            final int maxNameLen = 255 - ordinal.length();
-            nextName = (name.length() <= maxNameLen ? name : name.substring(0, maxNameLen)).concat(ordinal);
-        }
-        return nextName;
     }
 
     private void generateMethods() {
@@ -705,18 +581,25 @@
     }
 
     /**
-     * Generates a method in the adapter class that adapts a method from the original class. The generated methods will
-     * inspect the method handle field assigned to them. If it is null (the JS object doesn't provide an implementation
-     * for the method) then it will either invoke its version in the supertype, or if it is abstract, throw an
-     * {@link UnsupportedOperationException}. Otherwise, if the method handle field's value is not null, the handle is
-     * invoked using invokeExact (signature polymorphic invocation as per JLS 15.12.3). Before the invocation, the
-     * current Nashorn {@link Context} is checked, and if it is different than the global used to create the adapter
-     * instance, the creating global is set to be the current global. In this case, the previously current global is
-     * restored after the invocation. If invokeExact results in a Throwable that is not one of the method's declared
-     * exceptions, and is not an unchecked throwable, then it is wrapped into a {@link RuntimeException} and the runtime
-     * exception is thrown. The method handle retrieved from the field is guaranteed to exactly match the signature of
-     * the method; this is guaranteed by the way constructors of the adapter class obtain them using
-     * {@link #getHandle(Object, String, MethodType, boolean)}.
+     * Generates a method in the adapter class that adapts a method from the
+     * original class. The generated method will either invoke the delegate
+     * using a CALL dynamic operation call site (if it is a SAM method and the
+     * delegate is a ScriptFunction), or invoke GET_METHOD_PROPERTY dynamic
+     * operation with the method name as the argument and then invoke the
+     * returned ScriptFunction using the CALL dynamic operation. If
+     * GET_METHOD_PROPERTY returns null or undefined (that is, the JS object
+     * doesn't provide an implementation for the method) then the method will
+     * either do a super invocation to base class, or if the method is abstract,
+     * throw an {@link UnsupportedOperationException}. Finally, if
+     * GET_METHOD_PROPERTY returns something other than a ScriptFunction, null,
+     * or undefined, a TypeError is thrown. The current Global is checked before
+     * the dynamic operations, and if it is different  than the Global used to
+     * create the adapter, the creating Global is set to be the current Global.
+     * In this case, the previously current Global is restored after the
+     * invocation. If CALL results in a Throwable that is not one of the
+     * method's declared exceptions, and is not an unchecked throwable, then it
+     * is wrapped into a {@link RuntimeException} and the runtime exception is
+     * thrown.
      * @param mi the method info describing the method to be generated.
      */
     private void generateMethod(final MethodInfo mi) {
@@ -734,109 +617,158 @@
                 methodDesc, null, exceptionNames));
         mv.visitCode();
 
-        final Label handleDefined = new Label();
-
         final Class<?> returnType = type.returnType();
         final Type asmReturnType = Type.getType(returnType);
 
-        // See if we have overriding method handle defined
-        if(classOverride) {
-            mv.getstatic(generatedClassName, mi.methodHandleFieldName, METHOD_HANDLE_TYPE_DESCRIPTOR);
-        } else {
-            mv.visitVarInsn(ALOAD, 0);
-            mv.getfield(generatedClassName, mi.methodHandleFieldName, METHOD_HANDLE_TYPE_DESCRIPTOR);
-        }
-        // stack: [handle]
-        mv.visitInsn(DUP);
-        mv.visitJumpInsn(IFNONNULL, handleDefined);
-
-        // No handle is available, fall back to default behavior
-        if(Modifier.isAbstract(method.getModifiers())) {
-            // If the super method is abstract, throw an exception
-            mv.anew(UNSUPPORTED_OPERATION_TYPE);
-            mv.dup();
-            mv.invokespecial(UNSUPPORTED_OPERATION_TYPE_NAME, INIT, VOID_NOARG_METHOD_DESCRIPTOR, false);
-            mv.athrow();
-        } else {
-            mv.visitInsn(POP);
-            // If the super method is not abstract, delegate to it.
-            emitSuperCall(mv, method.getDeclaringClass(), name, methodDesc);
-        }
-
-        mv.visitLabel(handleDefined);
-        // Load the creatingGlobal object
-        if(classOverride) {
-            // If class handle is defined, load the static defining global
-            mv.getstatic(generatedClassName, GLOBAL_FIELD_NAME, GLOBAL_TYPE_DESCRIPTOR);
-        } else {
-            mv.visitVarInsn(ALOAD, 0);
-            mv.getfield(generatedClassName, GLOBAL_FIELD_NAME, GLOBAL_TYPE_DESCRIPTOR);
-        }
-        // stack: [creatingGlobal, handle]
-        final Label setupGlobal = new Label();
-        mv.visitLabel(setupGlobal);
-
         // Determine the first index for a local variable
         int nextLocalVar = 1; // "this" is at 0
         for(final Type t: asmArgTypes) {
             nextLocalVar += t.getSize();
         }
-        // Set our local variable indices
-        final int currentGlobalVar  = nextLocalVar++;
-        final int globalsDifferVar  = nextLocalVar++;
+        // Set our local variable index
+        final int globalRestoringRunnableVar = nextLocalVar++;
+
+        // Load the creatingGlobal object
+        loadField(mv, GLOBAL_FIELD_NAME, SCRIPT_OBJECT_TYPE_DESCRIPTOR);
+
+        // stack: [creatingGlobal]
+        SET_GLOBAL.invoke(mv);
+        // stack: [runnable]
+        mv.visitVarInsn(ASTORE, globalRestoringRunnableVar);
+        // stack: []
+
+        final Label tryBlockStart = new Label();
+        mv.visitLabel(tryBlockStart);
 
-        mv.dup();
-        // stack: [creatingGlobal, creatingGlobal, handle]
+        final Label callCallee = new Label();
+        final Label defaultBehavior = new Label();
+        // If this is a SAM type...
+        if (samName != null) {
+            // ...every method will be checking whether we're initialized with a
+            // function.
+            loadField(mv, IS_FUNCTION_FIELD_NAME, BOOLEAN_TYPE_DESCRIPTOR);
+            // stack: [isFunction]
+            if (name.equals(samName)) {
+                final Label notFunction = new Label();
+                mv.ifeq(notFunction);
+                // stack: []
+                // If it's a SAM method, it'll load delegate as the "callee" and
+                // "callThis" as "this" for the call if delegate is a function.
+                loadField(mv, DELEGATE_FIELD_NAME, SCRIPT_OBJECT_TYPE_DESCRIPTOR);
+                // NOTE: if we added "mv.checkcast(SCRIPT_FUNCTION_TYPE);" here
+                // we could emit the invokedynamic CALL instruction with signature
+                // (ScriptFunction, Object, ...) instead of (Object, Object, ...).
+                // We could combine this with an optimization in
+                // ScriptFunction.findCallMethod where it could link a call with a
+                // thinner guard when the call site statically guarantees that the
+                // callee argument is a ScriptFunction. Additionally, we could use
+                // a "ScriptFunction function" field in generated classes instead
+                // of a "boolean isFunction" field to avoid the checkcast.
+                loadField(mv, CALL_THIS_FIELD_NAME, OBJECT_TYPE_DESCRIPTOR);
+                // stack: [callThis, delegate]
+                mv.goTo(callCallee);
+                mv.visitLabel(notFunction);
+            } else {
+                // If it's not a SAM method, and the delegate is a function,
+                // it'll fall back to default behavior
+                mv.ifne(defaultBehavior);
+                // stack: []
+            }
+        }
 
-        // Emit code for switching to the creating global
-        // Global currentGlobal = Context.getGlobal();
-        invokeGetGlobal(mv);
-        mv.dup();
+        // At this point, this is either not a SAM method or the delegate is
+        // not a ScriptFunction. We need to emit a GET_METHOD_PROPERTY Nashorn
+        // invokedynamic.
 
-        mv.visitVarInsn(ASTORE, currentGlobalVar);
-        // stack: [currentGlobal, creatingGlobal, creatingGlobal, handle]
-        // if(definingGlobal == currentGlobal) {
-        final Label globalsDiffer = new Label();
-        mv.ifacmpne(globalsDiffer);
-        // stack: [creatingGlobal, handle]
-        //     globalsDiffer = false
-        mv.pop();
-        // stack: [handle]
-        mv.iconst(0); // false
-        // stack: [false, handle]
-        final Label invokeHandle = new Label();
-        mv.goTo(invokeHandle);
-        mv.visitLabel(globalsDiffer);
-        // } else {
-        //     Context.setGlobal(definingGlobal);
-        // stack: [creatingGlobal, handle]
-        invokeSetGlobal(mv);
-        // stack: [handle]
-        //     globalsDiffer = true
-        mv.iconst(1);
-        // stack: [true, handle]
+        if(name.equals("toString")) {
+            // Since every JS Object has a toString, we only override
+            // "String toString()" it if it's explicitly specified on the object.
+            loadField(mv, DELEGATE_FIELD_NAME, SCRIPT_OBJECT_TYPE_DESCRIPTOR);
+            // stack: [delegate]
+            HAS_OWN_TO_STRING.invoke(mv);
+            // stack: [hasOwnToString]
+            mv.ifeq(defaultBehavior);
+        }
+
+        loadField(mv, DELEGATE_FIELD_NAME, SCRIPT_OBJECT_TYPE_DESCRIPTOR);
+        mv.dup();
+        // stack: [delegate, delegate]
+        final String encodedName = NameCodec.encode(name);
+        mv.visitInvokeDynamicInsn(encodedName,
+                GET_METHOD_PROPERTY_METHOD_DESCRIPTOR, BOOTSTRAP_HANDLE,
+                NashornCallSiteDescriptor.GET_METHOD_PROPERTY);
+        // stack: [callee, delegate]
+        mv.visitLdcInsn(name);
+        // stack: [name, callee, delegate]
+        CHECK_FUNCTION.invoke(mv);
+        // stack: [fnCalleeOrNull, delegate]
+        final Label hasFunction = new Label();
+        mv.dup();
+        // stack: [fnCalleeOrNull, fnCalleeOrNull, delegate]
+        mv.ifnonnull(hasFunction);
+        // stack: [null, delegate]
+        // If it's null or undefined, clear stack and fall back to default
+        // behavior.
+        mv.pop2();
+        // stack: []
 
-        mv.visitLabel(invokeHandle);
-        mv.visitVarInsn(ISTORE, globalsDifferVar);
-        // stack: [handle]
+        // We can also arrive here from check for "delegate instanceof ScriptFunction"
+        // in a non-SAM method as well as from a check for "hasOwnToString(delegate)"
+        // for a toString delegate.
+        mv.visitLabel(defaultBehavior);
+        final Runnable emitFinally = ()->emitFinally(mv, globalRestoringRunnableVar);
+        final Label normalFinally = new Label();
+        if(Modifier.isAbstract(method.getModifiers())) {
+            // If the super method is abstract, throw UnsupportedOperationException
+            UNSUPPORTED.invoke(mv);
+            // NOTE: no need to invoke emitFinally.run() as we're inside the
+            // tryBlockStart/tryBlockEnd range, so throwing this exception will
+            // transfer control to the rethrow handler and the finally block in it
+            // will execute.
+            mv.athrow();
+        } else {
+            // If the super method is not abstract, delegate to it.
+            emitSuperCall(mv, method.getDeclaringClass(), name, methodDesc);
+            mv.goTo(normalFinally);
+        }
 
-        // Load all parameters back on stack for dynamic invocation. NOTE: since we're using a generic
-        // Object(Object, Object, ...) type signature for the method, we must box all arguments here.
+        mv.visitLabel(hasFunction);
+        // stack: [callee, delegate]
+        mv.swap();
+        // stack [delegate, callee]
+        mv.visitLabel(callCallee);
+
+
+        // Load all parameters back on stack for dynamic invocation.
+
         int varOffset = 1;
+        // If the param list length is more than 253 slots, we can't invoke it
+        // directly as with (callee, this) it'll exceed 255.
+        final boolean isVarArgCall = getParamListLengthInSlots(asmArgTypes) > 253;
         for (final Type t : asmArgTypes) {
             mv.load(varOffset, t);
-            boxStackTop(mv, t);
+            convertParam(mv, t, isVarArgCall);
             varOffset += t.getSize();
         }
+        // stack: [args..., callee, delegate]
+
+        // If the resulting parameter list length is too long...
+        if (isVarArgCall) {
+            // ... we pack the parameters (except callee and this) into an array
+            // and use Nashorn vararg invocation.
+            mv.visitInvokeDynamicInsn(NameCodec.EMPTY_NAME,
+                    getArrayCreatorMethodType(type).toMethodDescriptorString(),
+                    CREATE_ARRAY_BOOTSTRAP_HANDLE);
+        }
 
         // Invoke the target method handle
-        final Label tryBlockStart = new Label();
-        mv.visitLabel(tryBlockStart);
-        emitInvokeExact(mv, type.generic());
-        convertReturnValue(mv, returnType, asmReturnType);
-        final Label tryBlockEnd = new Label();
-        mv.visitLabel(tryBlockEnd);
-        emitFinally(mv, currentGlobalVar, globalsDifferVar);
+        mv.visitInvokeDynamicInsn(encodedName,
+                getCallMethodType(isVarArgCall, type).toMethodDescriptorString(),
+                BOOTSTRAP_HANDLE, NashornCallSiteDescriptor.CALL);
+        // stack: [returnValue]
+        convertReturnValue(mv, returnType);
+        mv.visitLabel(normalFinally);
+        emitFinally.run();
         mv.areturn(asmReturnType);
 
         // If Throwable is not declared, we need an adapter from Throwable to RuntimeException
@@ -846,10 +778,7 @@
             // Add "throw new RuntimeException(Throwable)" handler for Throwable
             throwableHandler = new Label();
             mv.visitLabel(throwableHandler);
-            mv.anew(RUNTIME_EXCEPTION_TYPE);
-            mv.dupX1();
-            mv.swap();
-            mv.invokespecial(RUNTIME_EXCEPTION_TYPE_NAME, INIT, Type.getMethodDescriptor(Type.VOID_TYPE, THROWABLE_TYPE), false);
+            WRAP_THROWABLE.invoke(mv);
             // Fall through to rethrow handler
         } else {
             throwableHandler = null;
@@ -857,149 +786,166 @@
         final Label rethrowHandler = new Label();
         mv.visitLabel(rethrowHandler);
         // Rethrow handler for RuntimeException, Error, and all declared exception types
-        emitFinally(mv, currentGlobalVar, globalsDifferVar);
+        emitFinally.run();
         mv.athrow();
-        final Label methodEnd = new Label();
-        mv.visitLabel(methodEnd);
-
-        mv.visitLocalVariable("currentGlobal", GLOBAL_TYPE_DESCRIPTOR, null, setupGlobal, methodEnd, currentGlobalVar);
-        mv.visitLocalVariable("globalsDiffer", Type.BOOLEAN_TYPE.getDescriptor(), null, setupGlobal, methodEnd, globalsDifferVar);
 
         if(throwableDeclared) {
-            mv.visitTryCatchBlock(tryBlockStart, tryBlockEnd, rethrowHandler, THROWABLE_TYPE_NAME);
+            mv.visitTryCatchBlock(tryBlockStart, normalFinally, rethrowHandler, THROWABLE_TYPE_NAME);
             assert throwableHandler == null;
         } else {
-            mv.visitTryCatchBlock(tryBlockStart, tryBlockEnd, rethrowHandler, RUNTIME_EXCEPTION_TYPE_NAME);
-            mv.visitTryCatchBlock(tryBlockStart, tryBlockEnd, rethrowHandler, ERROR_TYPE_NAME);
+            mv.visitTryCatchBlock(tryBlockStart, normalFinally, rethrowHandler, RUNTIME_EXCEPTION_TYPE_NAME);
+            mv.visitTryCatchBlock(tryBlockStart, normalFinally, rethrowHandler, ERROR_TYPE_NAME);
             for(final String excName: exceptionNames) {
-                mv.visitTryCatchBlock(tryBlockStart, tryBlockEnd, rethrowHandler, excName);
+                mv.visitTryCatchBlock(tryBlockStart, normalFinally, rethrowHandler, excName);
             }
-            mv.visitTryCatchBlock(tryBlockStart, tryBlockEnd, throwableHandler, THROWABLE_TYPE_NAME);
+            mv.visitTryCatchBlock(tryBlockStart, normalFinally, throwableHandler, THROWABLE_TYPE_NAME);
         }
         endMethod(mv);
     }
 
-    private void convertReturnValue(final InstructionAdapter mv, final Class<?> returnType, final Type asmReturnType) {
-        switch(asmReturnType.getSort()) {
-        case Type.VOID:
-            mv.pop();
-            break;
-        case Type.BOOLEAN:
-            JSType.TO_BOOLEAN.invoke(mv);
-            break;
-        case Type.BYTE:
-            JSType.TO_INT32.invoke(mv);
-            mv.visitInsn(Opcodes.I2B);
-            break;
-        case Type.SHORT:
-            JSType.TO_INT32.invoke(mv);
-            mv.visitInsn(Opcodes.I2S);
-            break;
-        case Type.CHAR:
-            // JSType doesn't have a TO_CHAR, so we have services supply us one.
-            mv.invokestatic(SERVICES_CLASS_TYPE_NAME, "toCharPrimitive", TO_CHAR_PRIMITIVE_METHOD_DESCRIPTOR, false);
-            break;
-        case Type.INT:
-            JSType.TO_INT32.invoke(mv);
-            break;
-        case Type.LONG:
-            JSType.TO_LONG.invoke(mv);
-            break;
-        case Type.FLOAT:
-            JSType.TO_NUMBER.invoke(mv);
-            mv.visitInsn(Opcodes.D2F);
-            break;
-        case Type.DOUBLE:
-            JSType.TO_NUMBER.invoke(mv);
-            break;
-        default:
-            if(asmReturnType.equals(OBJECT_TYPE)) {
-                // Must hide ConsString (and potentially other internal Nashorn types) from callers
-                mv.invokestatic(SERVICES_CLASS_TYPE_NAME, "exportReturnValue", EXPORT_RETURN_VALUE_METHOD_DESCRIPTOR, false);
-            } else if(asmReturnType.equals(STRING_TYPE)){
-                // Well-known conversion to String. Not using the JSType one as we want to preserve null as null instead
-                // of the string "n,u,l,l".
-                mv.invokestatic(SERVICES_CLASS_TYPE_NAME, "toString", TO_STRING_METHOD_DESCRIPTOR, false);
-            } else {
-                // Invoke converter method handle for everything else. Note that we could have just added an asType or
-                // filterReturnValue to the invoked handle instead, but then every instance would have the function
-                // method handle wrapped in a separate converter method handle, making handle.invokeExact() megamorphic.
-                if(classOverride) {
-                    mv.getstatic(generatedClassName, converterFields.get(returnType), METHOD_HANDLE_TYPE_DESCRIPTOR);
-                } else {
-                    mv.visitVarInsn(ALOAD, 0);
-                    mv.getfield(generatedClassName, converterFields.get(returnType), METHOD_HANDLE_TYPE_DESCRIPTOR);
-                }
-                mv.swap();
-                emitInvokeExact(mv, MethodType.methodType(returnType, Object.class));
+    private static MethodType getCallMethodType(final boolean isVarArgCall, final MethodType type) {
+        final Class<?>[] callParamTypes;
+        if (isVarArgCall) {
+            // Variable arity calls are always (Object callee, Object this, Object[] params)
+            callParamTypes = new Class<?>[] { Object.class, Object.class, Object[].class };
+        } else {
+            // Adjust invocation type signature for conversions we instituted in
+            // convertParam; also, byte and short get passed as ints.
+            final Class<?>[] origParamTypes = type.parameterArray();
+            callParamTypes = new Class<?>[origParamTypes.length + 2];
+            callParamTypes[0] = Object.class; // callee; could be ScriptFunction.class ostensibly
+            callParamTypes[1] = Object.class; // this
+            for(int i = 0; i < origParamTypes.length; ++i) {
+                callParamTypes[i + 2] = getNashornParamType(origParamTypes[i], false);
             }
         }
+        return MethodType.methodType(getNashornReturnType(type.returnType()), callParamTypes);
+    }
+
+    private static MethodType getArrayCreatorMethodType(final MethodType type) {
+        final Class<?>[] callParamTypes = type.parameterArray();
+        for(int i = 0; i < callParamTypes.length; ++i) {
+            callParamTypes[i] = getNashornParamType(callParamTypes[i], true);
+        }
+        return MethodType.methodType(Object[].class, callParamTypes);
+    }
+
+    private static Class<?> getNashornParamType(final Class<?> clazz, final boolean varArg) {
+        if (clazz == byte.class || clazz == short.class) {
+            return int.class;
+        } else if (clazz == float.class) {
+            // If using variable arity, we'll pass a Double instead of double
+            // so that floats don't extend the length of the parameter list.
+            // We return Object.class instead of Double.class though as the
+            // array collector will anyway operate on Object.
+            return varArg ? Object.class : double.class;
+        } else if (!clazz.isPrimitive() || clazz == long.class || clazz == char.class) {
+            return Object.class;
+        }
+        return clazz;
+    }
+
+    private static Class<?> getNashornReturnType(final Class<?> clazz) {
+        if (clazz == byte.class || clazz == short.class) {
+            return int.class;
+        } else if (clazz == float.class) {
+            return double.class;
+        } else if (clazz == void.class || clazz == char.class) {
+            return Object.class;
+        }
+        return clazz;
+    }
+
+
+    private void loadField(final InstructionAdapter mv, final String name, final String desc) {
+        if(classOverride) {
+            mv.getstatic(generatedClassName, name, desc);
+        } else {
+            mv.visitVarInsn(ALOAD, 0);
+            mv.getfield(generatedClassName, name, desc);
+        }
     }
 
-    private static void emitInvokeExact(final InstructionAdapter mv, final MethodType type) {
-        mv.invokevirtual(METHOD_HANDLE_TYPE.getInternalName(), "invokeExact", type.toMethodDescriptorString(), false);
+    private static void convertReturnValue(final InstructionAdapter mv, final Class<?> origReturnType) {
+        if (origReturnType == void.class) {
+            mv.pop();
+        } else if (origReturnType == Object.class) {
+            // Must hide ConsString (and potentially other internal Nashorn types) from callers
+            EXPORT_RETURN_VALUE.invoke(mv);
+        } else if (origReturnType == byte.class) {
+            mv.visitInsn(I2B);
+        } else if (origReturnType == short.class) {
+            mv.visitInsn(I2S);
+        } else if (origReturnType == float.class) {
+            mv.visitInsn(D2F);
+        } else if (origReturnType == char.class) {
+            TO_CHAR_PRIMITIVE.invoke(mv);
+        }
     }
 
-    private static void boxStackTop(final InstructionAdapter mv, final Type t) {
+    /**
+     * Emits instruction for converting a parameter on the top of the stack to
+     * a type that is understood by Nashorn.
+     * @param mv the current method visitor
+     * @param t the type on the top of the stack
+     * @param varArg if the invocation will be variable arity
+     */
+    private static void convertParam(final InstructionAdapter mv, final Type t, final boolean varArg) {
+        // We perform conversions of some primitives to accommodate types that
+        // Nashorn can handle.
         switch(t.getSort()) {
-        case Type.BOOLEAN:
-            invokeValueOf(mv, "Boolean", 'Z');
-            break;
-        case Type.BYTE:
-        case Type.SHORT:
-        case Type.INT:
-            // bytes and shorts get boxed as integers
-            invokeValueOf(mv, "Integer", 'I');
-            break;
         case Type.CHAR:
-            invokeValueOf(mv, "Character", 'C');
+            // Chars are boxed, as we don't know if the JS code wants to treat
+            // them as an effective "unsigned short" or as a single-char string.
+            CHAR_VALUE_OF.invoke(mv);
             break;
         case Type.FLOAT:
-            // floats get boxed as doubles
+            // Floats are widened to double.
             mv.visitInsn(Opcodes.F2D);
-            invokeValueOf(mv, "Double", 'D');
+            if (varArg) {
+                // We'll be boxing everything anyway for the vararg invocation,
+                // so we might as well do it proactively here and thus not cause
+                // a widening in the number of slots, as that could even make
+                // the array creation invocation go over 255 param slots.
+                DOUBLE_VALUE_OF.invoke(mv);
+            }
             break;
         case Type.LONG:
-            invokeValueOf(mv, "Long", 'J');
-            break;
-        case Type.DOUBLE:
-            invokeValueOf(mv, "Double", 'D');
-            break;
-        case Type.ARRAY:
-        case Type.METHOD:
-            // Already boxed
+            // Longs are boxed as Nashorn can't represent them precisely as a
+            // primitive number.
+            LONG_VALUE_OF.invoke(mv);
             break;
         case Type.OBJECT:
             if(t.equals(OBJECT_TYPE)) {
-                mv.invokestatic(SCRIPTUTILS_TYPE_NAME, "unwrap", UNWRAP_METHOD_DESCRIPTOR, false);
+                // Object can carry a ScriptObjectMirror and needs to be unwrapped
+                // before passing into a Nashorn function.
+                UNWRAP.invoke(mv);
             }
             break;
-        default:
-            // Not expecting anything else (e.g. VOID)
-            assert false;
-            break;
         }
     }
 
-    private static void invokeValueOf(final InstructionAdapter mv, final String boxedType, final char unboxedType) {
-        mv.invokestatic("java/lang/" + boxedType, "valueOf", "(" + unboxedType + ")Ljava/lang/" + boxedType + ";", false);
+    private static int getParamListLengthInSlots(final Type[] paramTypes) {
+        int len = paramTypes.length;
+        for(final Type t: paramTypes) {
+            final int sort = t.getSort();
+            if (sort == Type.FLOAT || sort == Type.DOUBLE) {
+                // Floats are widened to double, so they'll take up two slots.
+                // Longs on the other hand are always boxed, so their width
+                // becomes 1 and thus they don't contribute an extra slot here.
+                ++len;
+            }
+        }
+        return len;
     }
-
     /**
      * Emit code to restore the previous Nashorn Context when needed.
      * @param mv the instruction adapter
-     * @param currentGlobalVar index of the local variable holding the reference to the current global at method
-     * entry.
-     * @param globalsDifferVar index of the boolean local variable that is true if the global needs to be restored.
+     * @param globalRestoringRunnableVar index of the local variable holding the reference to the global restoring Runnable
      */
-    private static void emitFinally(final InstructionAdapter mv, final int currentGlobalVar, final int globalsDifferVar) {
-        // Emit code to restore the previous Nashorn global if needed
-        mv.visitVarInsn(ILOAD, globalsDifferVar);
-        final Label skip = new Label();
-        mv.ifeq(skip);
-        mv.visitVarInsn(ALOAD, currentGlobalVar);
-        invokeSetGlobal(mv);
-        mv.visitLabel(skip);
+    private static void emitFinally(final InstructionAdapter mv, final int globalRestoringRunnableVar) {
+        mv.visitVarInsn(ALOAD, globalRestoringRunnableVar);
+        RUN.invoke(mv);
     }
 
     private static boolean isThrowableDeclared(final Class<?>[] exceptions) {
@@ -1030,7 +976,7 @@
         mv.visitCode();
 
         emitSuperCall(mv, method.getDeclaringClass(), name, methodDesc);
-
+        mv.areturn(Type.getType(mi.type.returnType()));
         endMethod(mv);
     }
 
@@ -1052,7 +998,15 @@
         throw new AssertionError("can't find the class/interface that extends " + cl);
     }
 
-    private void emitSuperCall(final InstructionAdapter mv, final Class<?> owner, final String name, final String methodDesc) {
+    private int emitSuperConstructorCall(final InstructionAdapter mv, final String methodDesc) {
+        return emitSuperCall(mv, null, INIT, methodDesc, true);
+    }
+
+    private int emitSuperCall(final InstructionAdapter mv, final Class<?> owner, final String name, final String methodDesc) {
+        return emitSuperCall(mv, owner, name, methodDesc, false);
+    }
+
+    private int emitSuperCall(final InstructionAdapter mv, final Class<?> owner, final String name, final String methodDesc, final boolean constructor) {
         mv.visitVarInsn(ALOAD, 0);
         int nextParam = 1;
         final Type methodType = Type.getMethodType(methodDesc);
@@ -1062,48 +1016,46 @@
         }
 
         // default method - non-abstract, interface method
-        if (Modifier.isInterface(owner.getModifiers())) {
+        if (!constructor && Modifier.isInterface(owner.getModifiers())) {
             // we should call default method on the immediate "super" type - not on (possibly)
             // the indirectly inherited interface class!
             mv.invokespecial(Type.getInternalName(findInvokespecialOwnerFor(owner)), name, methodDesc, false);
         } else {
             mv.invokespecial(superClassName, name, methodDesc, false);
         }
-        mv.areturn(methodType.getReturnType());
+        return nextParam;
     }
 
     private void generateFinalizerMethods() {
-        final String finalizerDelegateName = nextName("access$");
-        generateFinalizerDelegate(finalizerDelegateName);
-        generateFinalizerOverride(finalizerDelegateName);
+        generateFinalizerDelegate();
+        generateFinalizerOverride();
     }
 
-    private void generateFinalizerDelegate(final String finalizerDelegateName) {
+    private void generateFinalizerDelegate() {
         // Generate a delegate that will be invoked from the no-permission trampoline. Note it can be private, as we'll
         // refer to it with a MethodHandle constant pool entry in the overridden finalize() method (see
         // generateFinalizerOverride()).
         final InstructionAdapter mv = new InstructionAdapter(cw.visitMethod(ACC_PRIVATE | ACC_STATIC,
-                finalizerDelegateName, Type.getMethodDescriptor(Type.VOID_TYPE, OBJECT_TYPE), null, null));
+                FINALIZER_DELEGATE_NAME, FINALIZER_DELEGATE_METHOD_DESCRIPTOR, null, null));
 
         // Simply invoke super.finalize()
         mv.visitVarInsn(ALOAD, 0);
         mv.checkcast(Type.getType(generatedClassName));
-        mv.invokespecial(superClassName, "finalize", Type.getMethodDescriptor(Type.VOID_TYPE), false);
+        mv.invokespecial(superClassName, "finalize", VOID_METHOD_DESCRIPTOR, false);
 
         mv.visitInsn(RETURN);
         endMethod(mv);
     }
 
-    private void generateFinalizerOverride(final String finalizerDelegateName) {
+    private void generateFinalizerOverride() {
         final InstructionAdapter mv = new InstructionAdapter(cw.visitMethod(ACC_PUBLIC, "finalize",
-                VOID_NOARG_METHOD_DESCRIPTOR, null, null));
+                VOID_METHOD_DESCRIPTOR, null, null));
         // Overridden finalizer will take a MethodHandle to the finalizer delegating method, ...
-        mv.aconst(new Handle(Opcodes.H_INVOKESTATIC, generatedClassName, finalizerDelegateName,
-                Type.getMethodDescriptor(Type.VOID_TYPE, OBJECT_TYPE)));
+        mv.aconst(new Handle(Opcodes.H_INVOKESTATIC, generatedClassName, FINALIZER_DELEGATE_NAME,
+                FINALIZER_DELEGATE_METHOD_DESCRIPTOR));
         mv.visitVarInsn(ALOAD, 0);
         // ...and invoke it through JavaAdapterServices.invokeNoPermissions
-        mv.invokestatic(SERVICES_CLASS_TYPE_NAME, "invokeNoPermissions",
-                Type.getMethodDescriptor(METHOD_HANDLE_TYPE, OBJECT_TYPE), false);
+        INVOKE_NO_PERMISSIONS.invoke(mv);
         mv.visitInsn(RETURN);
         endMethod(mv);
     }
@@ -1157,11 +1109,8 @@
                     final MethodInfo mi = new MethodInfo(typeMethod);
                     if (Modifier.isFinal(m) || isCallerSensitive(typeMethod)) {
                         finalMethods.add(mi);
-                    } else if (!finalMethods.contains(mi) && methodInfos.add(mi)) {
-                        if (Modifier.isAbstract(m)) {
-                            abstractMethodNames.add(mi.getName());
-                        }
-                        mi.setIsCanonical(this);
+                    } else if (!finalMethods.contains(mi) && methodInfos.add(mi) && Modifier.isAbstract(m)) {
+                        abstractMethodNames.add(mi.getName());
                     }
                 }
             }
@@ -1222,7 +1171,7 @@
                 return type2;
             }
             if (c1.isInterface() || c2.isInterface()) {
-                return OBJECT_TYPE_NAME;
+                return OBJECT_TYPE.getInternalName();
             }
             return assignableSuperClass(c1, c2).getName().replace('.', '/');
         } catch(final ClassNotFoundException e) {
@@ -1238,4 +1187,8 @@
     private static boolean isCallerSensitive(final AccessibleObject e) {
         return e.isAnnotationPresent(CallerSensitive.class);
     }
+
+    private static final Call lookupServiceMethod(final String name, final Class<?> rtype, final Class<?>... ptypes) {
+        return staticCallNoLookup(JavaAdapterServices.class, name, rtype, ptypes);
+    }
 }
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/linker/JavaAdapterFactory.java	Fri Jan 22 17:01:41 2016 +0100
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/linker/JavaAdapterFactory.java	Sat Jan 23 11:50:24 2016 +0100
@@ -54,6 +54,7 @@
 import jdk.nashorn.internal.runtime.ECMAException;
 import jdk.nashorn.internal.runtime.ScriptFunction;
 import jdk.nashorn.internal.runtime.ScriptObject;
+import jdk.nashorn.internal.runtime.linker.AdaptationResult.Outcome;
 
 /**
  * A factory class that generates adapter classes. Adapter classes allow
@@ -211,7 +212,7 @@
      *         be generated from a ScriptFunction.
      */
     static boolean isAutoConvertibleFromFunction(final Class<?> clazz) {
-        return getAdapterInfo(new Class<?>[] { clazz }).autoConvertibleFromFunction;
+        return getAdapterInfo(new Class<?>[] { clazz }).isAutoConvertibleFromFunction();
     }
 
     private static AdapterInfo getAdapterInfo(final Class<?>[] types) {
@@ -273,7 +274,7 @@
                 } catch (final AdaptationException e) {
                     return new AdapterInfo(e.getAdaptationResult());
                 } catch (final RuntimeException e) {
-                    return new AdapterInfo(new AdaptationResult(AdaptationResult.Outcome.ERROR_OTHER, Arrays.toString(types), e.toString()));
+                    return new AdapterInfo(new AdaptationResult(Outcome.ERROR_OTHER, e, Arrays.toString(types), e.toString()));
                 }
             }
         }, CREATE_ADAPTER_INFO_ACC_CTXT);
@@ -319,6 +320,13 @@
                 getClassAdapterClass(classOverrides, protectionDomain);
         }
 
+        boolean isAutoConvertibleFromFunction() {
+            if(adaptationResult.getOutcome() == AdaptationResult.Outcome.ERROR_OTHER) {
+                throw adaptationResult.typeError();
+            }
+            return autoConvertibleFromFunction;
+        }
+
         private StaticClass getInstanceAdapterClass(final ProtectionDomain protectionDomain) {
             CodeSource codeSource = protectionDomain.getCodeSource();
             if(codeSource == null) {
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/linker/JavaAdapterServices.java	Fri Jan 22 17:01:41 2016 +0100
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/linker/JavaAdapterServices.java	Sat Jan 23 11:50:24 2016 +0100
@@ -33,8 +33,11 @@
 import static jdk.internal.org.objectweb.asm.Opcodes.RETURN;
 import static jdk.nashorn.internal.runtime.ECMAErrors.typeError;
 
+import java.lang.invoke.CallSite;
+import java.lang.invoke.ConstantCallSite;
 import java.lang.invoke.MethodHandle;
 import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodHandles.Lookup;
 import java.lang.invoke.MethodType;
 import java.security.AccessController;
 import java.security.CodeSigner;
@@ -43,15 +46,18 @@
 import java.security.PrivilegedAction;
 import java.security.ProtectionDomain;
 import java.security.SecureClassLoader;
+import java.util.Objects;
 import jdk.internal.org.objectweb.asm.ClassWriter;
 import jdk.internal.org.objectweb.asm.Opcodes;
 import jdk.internal.org.objectweb.asm.Type;
 import jdk.internal.org.objectweb.asm.commons.InstructionAdapter;
+import jdk.nashorn.internal.objects.Global;
 import jdk.nashorn.internal.runtime.Context;
+import jdk.nashorn.internal.runtime.ECMAException;
+import jdk.nashorn.internal.runtime.JSType;
 import jdk.nashorn.internal.runtime.ScriptFunction;
 import jdk.nashorn.internal.runtime.ScriptObject;
 import jdk.nashorn.internal.runtime.ScriptRuntime;
-import jdk.nashorn.internal.runtime.Undefined;
 
 /**
  * Provides static utility services to generated Java adapter classes.
@@ -64,50 +70,47 @@
     }
 
     /**
-     * Given a JS script function, binds it to null JS "this", and adapts its parameter types, return types, and arity
-     * to the specified type and arity. This method is public mainly for implementation reasons, so the adapter classes
-     * can invoke it from their constructors that take a ScriptFunction in its first argument to obtain the method
-     * handles for their abstract method implementations.
-     * @param fn the script function
-     * @param type the method type it has to conform to
-     * @return the appropriately adapted method handle for invoking the script function.
+     * Given a script function used as a delegate for a SAM adapter, figure out
+     * the right object to use as its "this" when called.
+     * @param delegate the delegate function
+     * @param global the current global of the adapter
+     * @return either the passed global, or UNDEFINED if the function is strict.
      */
-    public static MethodHandle getHandle(final ScriptFunction fn, final MethodType type) {
-        // JS "this" will be global object or undefined depending on if 'fn' is strict or not
-        return bindAndAdaptHandle(fn, fn.isStrict()? ScriptRuntime.UNDEFINED : Context.getGlobal(), type);
+    public static Object getCallThis(final ScriptFunction delegate, final Object global) {
+        return delegate.isStrict() ? ScriptRuntime.UNDEFINED : global;
+    }
+
+    /**
+     * Throws a "not.an.object" type error. Used when the delegate passed to the
+     * adapter constructor is not a script object.
+     * @param obj the object that is not a script object.
+     */
+    public static void notAnObject(final Object obj) {
+        throw typeError("not.an.object", ScriptRuntime.safeToString(obj));
     }
 
     /**
-     * Given a JS script object, retrieves a function from it by name, binds it to the script object as its "this", and
-     * adapts its parameter types, return types, and arity to the specified type and arity. This method is public mainly
-     * for implementation reasons, so the adapter classes can invoke it from their constructors that take a Object
-     * in its first argument to obtain the method handles for their method implementations.
-     * @param obj the script obj
-     * @param name the name of the property that contains the function
-     * @param type the method type it has to conform to
-     * @return the appropriately adapted method handle for invoking the script function, or null if the value of the
-     * property is either null or undefined, or "toString" was requested as the name, but the object doesn't directly
-     * define it but just inherits it through prototype.
+     * Checks if the passed object, which is supposed to be a callee retrieved
+     * through applying the GET_METHOD_PROPERTY operation on the delegate, is
+     * a ScriptFunction, or null or undefined. These are the only allowed values
+     * for adapter method implementations, so in case it is neither, it throws
+     * a type error. Note that this restriction is somewhat artificial; as the
+     * CALL dynamic operation could invoke any Nashorn callable. We are
+     * restricting adapters to actual ScriptFunction objects for now though.
+     * @param callee the callee to check
+     * @param name the name of the function
+     * @return the callee cast to a ScriptFunction, or null if it was null or undefined.
+     * @throws ECMAException representing a JS TypeError with "not.a.function"
+     * message if the passed callee is neither a script function, nor null, nor
+     * undefined.
      */
-    public static MethodHandle getHandle(final Object obj, final String name, final MethodType type) {
-        if (! (obj instanceof ScriptObject)) {
-            throw typeError("not.an.object", ScriptRuntime.safeToString(obj));
-        }
-
-        final ScriptObject sobj = (ScriptObject)obj;
-        // Since every JS Object has a toString, we only override "String toString()" it if it's explicitly specified
-        if ("toString".equals(name) && !sobj.hasOwnProperty("toString")) {
+    public static ScriptFunction checkFunction(final Object callee, final String name) {
+        if (callee instanceof ScriptFunction) {
+            return (ScriptFunction)callee;
+        } else if (JSType.nullOrUndefined(callee)) {
             return null;
         }
-
-        final Object fnObj = sobj.get(name);
-        if (fnObj instanceof ScriptFunction) {
-            return bindAndAdaptHandle((ScriptFunction)fnObj, sobj, type);
-        } else if(fnObj == null || fnObj instanceof Undefined) {
-            return null;
-        } else {
-            throw typeError("not.a.function", name);
-        }
+        throw typeError("not.a.function.value", name, ScriptRuntime.safeToString(callee));
     }
 
     /**
@@ -116,8 +119,8 @@
      * static initializers.
      * @return the thread-local JS object used to define methods for the class being initialized.
      */
-    public static Object getClassOverrides() {
-        final Object overrides = classOverrides.get();
+    public static ScriptObject getClassOverrides() {
+        final ScriptObject overrides = classOverrides.get();
         assert overrides != null;
         return overrides;
     }
@@ -135,29 +138,59 @@
     }
 
     /**
-     * Set the current global scope
-     * @param global the global scope
+     * Set the current global scope to that of the adapter global
+     * @param adapterGlobal the adapter's global scope
+     * @return a Runnable that when invoked restores the previous global
      */
-    public static void setGlobal(final Object global) {
-        Context.setGlobal((ScriptObject)global);
+    public static Runnable setGlobal(final ScriptObject adapterGlobal) {
+        final Global currentGlobal = Context.getGlobal();
+        if (adapterGlobal != currentGlobal) {
+            Context.setGlobal(adapterGlobal);
+            return ()->Context.setGlobal(currentGlobal);
+        }
+        return ()->{};
+    }
+
+    /**
+     * Get the current non-null global scope
+     * @return the current global scope
+     * @throws NullPointerException if the current global scope is null.
+     */
+    public static ScriptObject getNonNullGlobal() {
+        return Objects.requireNonNull(Context.getGlobal(), "Current global is null");
     }
 
     /**
-     * Get the current global scope
-     * @return the current global scope
+     * Returns true if the object has its own toString function. Used
+     * when implementing toString for adapters. Since every JS Object has a
+     * toString function, we only override "String toString()" in adapters if
+     * it is explicitly specified and not inherited from a prototype.
+     * @param sobj the object
+     * @return true if the object has its own toString function.
      */
-    public static Object getGlobal() {
-        return Context.getGlobal();
+    public static boolean hasOwnToString(final ScriptObject sobj) {
+        // NOTE: we could just use ScriptObject.hasOwnProperty("toString"), but
+        // its logic is more complex and this is what it boils down to with a
+        // fixed "toString" argument.
+        return sobj.getMap().findProperty("toString") != null;
+    }
+
+    /**
+     * Delegate to {@link Bootstrap#bootstrap(Lookup, String, MethodType, int)}.
+     * @param lookup MethodHandle lookup.
+     * @param opDesc Dynalink dynamic operation descriptor.
+     * @param type   Method type.
+     * @param flags  flags for call type, trace/profile etc.
+     * @return CallSite with MethodHandle to appropriate method or null if not found.
+     */
+    public static CallSite bootstrap(final Lookup lookup, final String opDesc, final MethodType type, final int flags) {
+        return Bootstrap.bootstrap(lookup, opDesc, type, flags);
     }
 
     static void setClassOverrides(final ScriptObject overrides) {
         classOverrides.set(overrides);
     }
 
-    private static MethodHandle bindAndAdaptHandle(final ScriptFunction fn, final Object self, final MethodType type) {
-        return Bootstrap.getLinkerServices().asType(ScriptObject.pairArguments(fn.getBoundInvokeHandle(self), type, false), type);
-    }
-
     private static MethodHandle createNoPermissionsInvoker() {
         final String className = "NoPermissionsInvoker";
 
@@ -203,16 +236,6 @@
     }
 
     /**
-     * Returns a method handle used to convert a return value from a delegate method (always Object) to the expected
-     * Java return type.
-     * @param returnType the return type
-     * @return the converter for the expected return type
-     */
-    public static MethodHandle getObjectConverter(final Class<?> returnType) {
-        return Bootstrap.getLinkerServices().getTypeConverter(Object.class, returnType);
-    }
-
-    /**
      * Invoked when returning Object from an adapted method to filter out internal Nashorn objects that must not be seen
      * by the callers. Currently only transforms {@code ConsString} into {@code String} and transforms {@code ScriptObject} into {@code ScriptObjectMirror}.
      * @param obj the return value
@@ -233,13 +256,39 @@
     }
 
     /**
-     * Invoked to convert a return value of a delegate function to String. It is similar to
-     * {@code JSType.toString(Object)}, except it doesn't handle StaticClass specially, and it returns null for null
-     * input instead of the string "null".
-     * @param obj the return value.
-     * @return the String value of the return value
+     * Returns a new {@link RuntimeException} wrapping the passed throwable.
+     * Makes generated bytecode smaller by doing an INVOKESTATIC to this method
+     * rather than the NEW/DUP_X1/SWAP/INVOKESPECIAL &lt;init&gt; sequence.
+     * @param t the original throwable to wrap
+     * @return a newly created runtime exception wrapping the passed throwable.
+     */
+    public static RuntimeException wrapThrowable(final Throwable t) {
+        return new RuntimeException(t);
+    }
+
+    /**
+     * Creates and returns a new {@link UnsupportedOperationException}. Makes
+     * generated bytecode smaller by doing INVOKESTATIC to this method rather
+     * than the NEW/DUP/INVOKESPECIAL &lt;init&gt; sequence.
+     * @return a newly created {@link UnsupportedOperationException}.
      */
-    public static String toString(final Object obj) {
-        return JavaArgumentConverters.toString(obj);
+    public static UnsupportedOperationException unsupported() {
+        return new UnsupportedOperationException();
+    }
+
+    /**
+     * A bootstrap method used to collect invocation arguments into an Object array.
+     * for variable arity invocation.
+     * @param lookup the adapter's lookup (not used).
+     * @param name the call site name (not used).
+     * @param type the method type
+     * @return a method that takes the input parameters and packs them into a
+     * newly allocated Object array.
+     */
+    public static CallSite createArrayBootstrap(final MethodHandles.Lookup lookup, final String name, final MethodType type) {
+        return new ConstantCallSite(
+                MethodHandles.identity(Object[].class)
+                .asCollector(Object[].class, type.parameterCount())
+                .asType(type));
     }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/nashorn/test/src/jdk/nashorn/internal/runtime/linker/test/JavaAdapterTest.java	Sat Jan 23 11:50:24 2016 +0100
@@ -0,0 +1,280 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.nashorn.internal.runtime.linker.test;
+
+import java.util.Collection;
+import java.util.Deque;
+import java.util.List;
+import java.util.Map;
+import java.util.Queue;
+import java.util.function.Supplier;
+import javax.script.Bindings;
+import javax.script.ScriptEngine;
+import javax.script.ScriptException;
+import jdk.nashorn.api.scripting.JSObject;
+import jdk.nashorn.api.scripting.NashornScriptEngineFactory;
+import jdk.nashorn.api.scripting.ScriptObjectMirror;
+import jdk.nashorn.internal.runtime.Context;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+public class JavaAdapterTest {
+    public interface TestConversions {
+        public byte getByte(byte b);
+        public short getShort(short b);
+        public char getChar(char c);
+        public int getInt(int i);
+        public float getFloat(float f);
+        public long getLong(long l);
+        public double getDouble(double d);
+    }
+
+    @Test
+    public static void testBlah() throws ScriptException {
+        final ScriptEngine e = createEngine();
+        e.eval("new java.util.Comparator({})");
+    }
+
+    @Test
+    public static void testConversions() throws ScriptException {
+        final ScriptEngine e = createEngine();
+        e.put("TestConversionsClass", TestConversions.class);
+        final TestConversions tc = (TestConversions)e.eval(
+                "function id(x) { return x };" +
+                "new TestConversionsClass.static({" +
+                "  getByte: id, getShort: id, getChar: id, getInt: id," +
+                "  getFloat: id, getLong: id, getDouble: id });");
+
+        Assert.assertEquals(Byte.MIN_VALUE, tc.getByte(Byte.MIN_VALUE));
+        Assert.assertEquals(Byte.MAX_VALUE, tc.getByte(Byte.MAX_VALUE));
+
+        Assert.assertEquals(Short.MIN_VALUE, tc.getShort(Short.MIN_VALUE));
+        Assert.assertEquals(Short.MAX_VALUE, tc.getShort(Short.MAX_VALUE));
+
+        Assert.assertEquals(Character.MIN_VALUE, tc.getChar(Character.MIN_VALUE));
+        Assert.assertEquals(Character.MAX_VALUE, tc.getChar(Character.MAX_VALUE));
+
+        Assert.assertEquals(Integer.MIN_VALUE, tc.getInt(Integer.MIN_VALUE));
+        Assert.assertEquals(Integer.MAX_VALUE, tc.getInt(Integer.MAX_VALUE));
+
+        Assert.assertEquals(Long.MIN_VALUE, tc.getLong(Long.MIN_VALUE));
+        Assert.assertEquals(Long.MAX_VALUE, tc.getLong(Long.MAX_VALUE));
+
+        Assert.assertEquals(Float.MIN_VALUE, tc.getFloat(Float.MIN_VALUE));
+        Assert.assertEquals(Float.MAX_VALUE, tc.getFloat(Float.MAX_VALUE));
+        Assert.assertEquals(Float.MIN_NORMAL, tc.getFloat(Float.MIN_NORMAL));
+        Assert.assertEquals(Float.POSITIVE_INFINITY, tc.getFloat(Float.POSITIVE_INFINITY));
+        Assert.assertEquals(Float.NEGATIVE_INFINITY, tc.getFloat(Float.NEGATIVE_INFINITY));
+        Assert.assertTrue(Float.isNaN(tc.getFloat(Float.NaN)));
+
+        Assert.assertEquals(Double.MIN_VALUE, tc.getDouble(Double.MIN_VALUE));
+        Assert.assertEquals(Double.MAX_VALUE, tc.getDouble(Double.MAX_VALUE));
+        Assert.assertEquals(Double.MIN_NORMAL, tc.getDouble(Double.MIN_NORMAL));
+        Assert.assertEquals(Double.POSITIVE_INFINITY, tc.getDouble(Double.POSITIVE_INFINITY));
+        Assert.assertEquals(Double.NEGATIVE_INFINITY, tc.getDouble(Double.NEGATIVE_INFINITY));
+        Assert.assertTrue(Double.isNaN(tc.getDouble(Double.NaN)));
+    }
+
+    private static ScriptEngine createEngine() {
+        // Use no optimistic typing so we run faster; short-running tests.
+        return new NashornScriptEngineFactory().getScriptEngine("-ot=false");
+    }
+
+    @Test
+    public static void testUnimplemented() throws ScriptException {
+        final ScriptEngine e = createEngine();
+        final Runnable r = (Runnable) e.eval("new java.lang.Runnable({})");
+        Assert.assertNull(Context.getGlobal());
+        try {
+            r.run();
+            Assert.fail();
+        } catch(final UnsupportedOperationException x) {
+            // This is expected
+        }
+        // Check global has been restored
+        Assert.assertNull(Context.getGlobal());
+    }
+
+    public interface ThrowingRunnable {
+        public void run() throws Throwable;
+    }
+
+    @Test
+    public static void testUnimplementedWithThrowable() throws Throwable {
+        final ScriptEngine e = createEngine();
+        e.put("ThrowingRunnableClass", ThrowingRunnable.class);
+        final ThrowingRunnable r = (ThrowingRunnable) e.eval("new ThrowingRunnableClass.static({})");
+        Assert.assertNull(Context.getGlobal());
+        try {
+            r.run();
+            Assert.fail();
+        } catch(final UnsupportedOperationException x) {
+            // This is expected
+        }
+        // Check global has been restored
+        Assert.assertNull(Context.getGlobal());
+    }
+
+    public interface IntSupplierWithDefault {
+        public default int get() { return 42; }
+    }
+
+    @Test
+    public static void testUnimplementedWithDefault() throws ScriptException {
+        final ScriptEngine e = createEngine();
+        e.put("IntSupplierWithDefault", IntSupplierWithDefault.class);
+        final IntSupplierWithDefault s1 = (IntSupplierWithDefault) e.eval("new IntSupplierWithDefault.static({})");
+        Assert.assertEquals(42, s1.get());
+        final IntSupplierWithDefault s2 = (IntSupplierWithDefault) e.eval("new IntSupplierWithDefault.static({ get: function() { return 43 }})");
+        Assert.assertEquals(43, s2.get());
+    }
+
+    public interface SupplierSupplier {
+        public Supplier<Object> getSupplier();
+    }
+
+    @Test
+    public static void testReturnAdapter() throws ScriptException {
+        final ScriptEngine e = createEngine();
+        e.put("SupplierSupplier", SupplierSupplier.class);
+        final SupplierSupplier s = (SupplierSupplier) e.eval("new SupplierSupplier.static(function(){ return function() { return 'foo' } })");
+        Assert.assertEquals("foo", s.getSupplier().get());
+    }
+
+    public interface MaxParams {
+        public Object method(boolean p1, byte p2, short p3, char p4, int p5, float p6, long p7, double p8,
+                Object p9, Object p10, Object p11, Object p12, Object p13, Object p14, Object p15, Object p16,
+                Object p17, Object p18, Object p19, Object p20, Object p21, Object p22, Object p23, Object p24,
+                Object p25, Object p26, Object p27, Object p28, Object p29, Object p30, Object p31, Object p32,
+                Object p33, Object p34, Object p35, Object p36, Object p37, Object p38, Object p39, Object p40,
+                Object p41, Object p42, Object p43, Object p44, Object p45, Object p46, Object p47, Object p48,
+                Object p49, Object p50, Object p51, Object p52, Object p53, Object p54, Object p55, Object p56,
+                Object p57, Object p58, Object p59, Object p60, Object p61, Object p62, Object p63, Object p64,
+                Object p65, Object p66, Object p67, Object p68, Object p69, Object p70, Object p71, Object p72,
+                Object p73, Object p74, Object p75, Object p76, Object p77, Object p78, Object p79, Object p80,
+                Object p81, Object p82, Object p83, Object p84, Object p85, Object p86, Object p87, Object p88,
+                Object p89, Object p90, Object p91, Object p92, Object p93, Object p94, Object p95, Object p96,
+                Object p97, Object p98, Object p99, Object p100, Object p101, Object p102, Object p103, Object p104,
+                Object p105, Object p106, Object p107, Object p108, Object p109, Object p110, Object p111, Object p112,
+                Object p113, Object p114, Object p115, Object p116, Object p117, Object p118, Object p119, Object p120,
+                Object p121, Object p122, Object p123, Object p124, Object p125, Object p126, Object p127, Object p128,
+                Object p129, Object p130, Object p131, Object p132, Object p133, Object p134, Object p135, Object p136,
+                Object p137, Object p138, Object p139, Object p140, Object p141, Object p142, Object p143, Object p144,
+                Object p145, Object p146, Object p147, Object p148, Object p149, Object p150, Object p151, Object p152,
+                Object p153, Object p154, Object p155, Object p156, Object p157, Object p158, Object p159, Object p160,
+                Object p161, Object p162, Object p163, Object p164, Object p165, Object p166, Object p167, Object p168,
+                Object p169, Object p170, Object p171, Object p172, Object p173, Object p174, Object p175, Object p176,
+                Object p177, Object p178, Object p179, Object p180, Object p181, Object p182, Object p183, Object p184,
+                Object p185, Object p186, Object p187, Object p188, Object p189, Object p190, Object p191, Object p192,
+                Object p193, Object p194, Object p195, Object p196, Object p197, Object p198, Object p199, Object p200,
+                Object p201, Object p202, Object p203, Object p204, Object p205, Object p206, Object p207, Object p208,
+                Object p209, Object p210, Object p211, Object p212, Object p213, Object p214, Object p215, Object p216,
+                Object p217, Object p218, Object p219, Object p220, Object p221, Object p222, Object p223, Object p224,
+                Object p225, Object p226, Object p227, Object p228, Object p229, Object p230, Object p231, Object p232,
+                Object p233, Object p234, Object p235, Object p236, Object p237, Object p238, Object p239, Object p240,
+                Object p241, Object p242, Object p243, Object p244, Object p245, Object p246, Object p247, Object p248,
+                Object p249, Object p250, Object p251, Object p252);
+    }
+
+    @Test
+    public static void testMaxLengthAdapter() throws ScriptException {
+        final ScriptEngine e = createEngine();
+        e.put("MaxParams", MaxParams.class);
+        final MaxParams s = (MaxParams) e.eval("new MaxParams.static(function(){ return arguments })");
+        final ScriptObjectMirror m = (ScriptObjectMirror)s.method(true, Byte.MIN_VALUE, Short.MIN_VALUE, 'a', Integer.MAX_VALUE, Float.MAX_VALUE, Long.MAX_VALUE, Double.MAX_VALUE,
+                "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26",
+                "27", "28", "29", "30", "31", "32", "33", "34", "35", "36", "37", "38", "39", "40", "41", "42", "43", "44",
+                "45", "46", "47", "48", "49", "50", "51", "52", "53", "54", "55", "56", "57", "58", "59", "60", "61", "62",
+                "63", "64", "65", "66", "67", "68", "69", "70", "71", "72", "73", "74", "75", "76", "77", "78", "79", "80",
+                "81", "82", "83", "84", "85", "86", "87", "88", "89", "90", "91", "92", "93", "94", "95", "96", "97", "98",
+                "99", "100", "101", "102", "103", "104", "105", "106", "107", "108", "109", "110", "111", "112", "113", "114",
+                "115", "116", "117", "118", "119", "120", "121", "122", "123", "124", "125", "126", "127", "128", "129", "130",
+                "131", "132", "133", "134", "135", "136", "137", "138", "139", "140", "141", "142", "143", "144", "145", "146",
+                "147", "148", "149", "150", "151", "152", "153", "154", "155", "156", "157", "158", "159", "160", "161", "162",
+                "163", "164", "165", "166", "167", "168", "169", "170", "171", "172", "173", "174", "175", "176", "177", "178",
+                "179", "180", "181", "182", "183", "184", "185", "186", "187", "188", "189", "190", "191", "192", "193", "194",
+                "195", "196", "197", "198", "199", "200", "201", "202", "203", "204", "205", "206", "207", "208", "209", "210",
+                "211", "212", "213", "214", "215", "216", "217", "218", "219", "220", "221", "222", "223", "224", "225", "226",
+                "227", "228", "229", "230", "231", "232", "233", "234", "235", "236", "237", "238", "239", "240", "241", "242",
+                "243", "244", "245", "246", "247", "248", "249", "250", "251");
+        Assert.assertEquals(true, m.getSlot(0));
+        Assert.assertEquals(Integer.valueOf(Byte.MIN_VALUE), m.getSlot(1)); // Byte becomes Integer
+        Assert.assertEquals(Integer.valueOf(Short.MIN_VALUE), m.getSlot(2)); // Short becomes Integer
+        Assert.assertEquals(Character.valueOf('a'), m.getSlot(3));
+        Assert.assertEquals(Integer.valueOf(Integer.MAX_VALUE), m.getSlot(4));
+        Assert.assertEquals(Double.valueOf(Float.MAX_VALUE), m.getSlot(5)); // Float becomes Double
+        Assert.assertEquals(Long.valueOf(Long.MAX_VALUE), m.getSlot(6)); // Long was untouched
+        Assert.assertEquals(Double.valueOf(Double.MAX_VALUE), m.getSlot(7));
+        for (int i = 8; i < 252; ++i) {
+            Assert.assertEquals(String.valueOf(i), m.getSlot(i));
+        }
+    }
+
+    public interface TestScriptObjectMirror {
+        public JSObject getJSObject();
+        public ScriptObjectMirror getScriptObjectMirror();
+        public Map<Object, Object> getMap();
+        public Bindings getBindings();
+    }
+
+    @Test
+    public static void testReturnsScriptObjectMirror() throws ScriptException {
+        final ScriptEngine e = createEngine();
+        e.put("TestScriptObjectMirrorClass", TestScriptObjectMirror.class);
+        final TestScriptObjectMirror tsom = (TestScriptObjectMirror)e.eval(
+                "new TestScriptObjectMirrorClass.static({\n" +
+                "  getJSObject: function() { return { 'kind': 'JSObject' } },\n" +
+                "  getScriptObjectMirror: function() { return { 'kind': 'ScriptObjectMirror' } },\n" +
+                "  getMap: function() { return { 'kind': 'Map' } },\n" +
+                "  getBindings: function() { return { 'kind': 'Bindings' } } })\n");
+        Assert.assertEquals(tsom.getJSObject().getMember("kind"), "JSObject");
+        Assert.assertEquals(tsom.getScriptObjectMirror().getMember("kind"), "ScriptObjectMirror");
+        Assert.assertEquals(tsom.getMap().get("kind"), "Map");
+        Assert.assertEquals(tsom.getBindings().get("kind"), "Bindings");
+    }
+
+    public interface TestListAdapter {
+        public List<Object> getList();
+        public Collection<Object> getCollection();
+        public Queue<Object> getQueue();
+        public Deque<Object> getDequeue();
+    }
+
+    @Test
+    public static void testReturnsListAdapter() throws ScriptException {
+        final ScriptEngine e = createEngine();
+        e.put("TestListAdapterClass", TestListAdapter.class);
+        final TestListAdapter tla = (TestListAdapter)e.eval(
+                "new TestListAdapterClass.static({\n" +
+                "  getList: function() { return [ 'List' ] },\n" +
+                "  getCollection: function() { return [ 'Collection' ] },\n" +
+                "  getQueue: function() { return [ 'Queue' ] },\n" +
+                "  getDequeue: function() { return [ 'Dequeue' ] } })\n");
+        Assert.assertEquals(tla.getList().get(0), "List");
+        Assert.assertEquals(tla.getCollection().iterator().next(), "Collection");
+        Assert.assertEquals(tla.getQueue().peek(), "Queue");
+        Assert.assertEquals(tla.getDequeue().peek(), "Dequeue");
+    }
+}