changeset 852:fd32489a1cf1

8043004: Reduce variability at JavaAdapter call sites Reviewed-by: lagergren, sundar
author attila
date Wed, 14 May 2014 15:55:27 +0200
parents fbca2b7761ae
children 1b93607e77f8
files src/jdk/nashorn/internal/codegen/CompilationPhase.java src/jdk/nashorn/internal/codegen/CompilerConstants.java src/jdk/nashorn/internal/codegen/DumpBytecode.java src/jdk/nashorn/internal/runtime/linker/JavaAdapterBytecodeGenerator.java src/jdk/nashorn/internal/runtime/linker/JavaAdapterClassLoader.java src/jdk/nashorn/internal/runtime/linker/JavaAdapterServices.java src/jdk/nashorn/internal/runtime/linker/JavaArgumentConverters.java src/jdk/nashorn/internal/runtime/linker/NashornBeansLinker.java src/jdk/nashorn/internal/runtime/linker/NashornLinker.java
diffstat 9 files changed, 318 insertions(+), 85 deletions(-) [+]
line wrap: on
line diff
--- a/src/jdk/nashorn/internal/codegen/CompilationPhase.java	Wed May 14 10:51:39 2014 +0200
+++ b/src/jdk/nashorn/internal/codegen/CompilationPhase.java	Wed May 14 15:55:27 2014 +0200
@@ -232,7 +232,7 @@
                     compiler.getCodeInstaller().verify(bytecode);
                 }
 
-                DumpBytecode.dumpBytecode(env, compiler, bytecode, className);
+                DumpBytecode.dumpBytecode(env, compiler.getLogger(), bytecode, className);
             }
 
             return newFunctionNode;
--- a/src/jdk/nashorn/internal/codegen/CompilerConstants.java	Wed May 14 10:51:39 2014 +0200
+++ b/src/jdk/nashorn/internal/codegen/CompilerConstants.java	Wed May 14 15:55:27 2014 +0200
@@ -32,6 +32,8 @@
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.Set;
+import jdk.internal.org.objectweb.asm.MethodVisitor;
+import jdk.internal.org.objectweb.asm.Opcodes;
 import jdk.nashorn.internal.codegen.types.Type;
 import jdk.nashorn.internal.runtime.ScriptFunction;
 import jdk.nashorn.internal.runtime.ScriptObject;
@@ -362,9 +364,14 @@
     public static Call specialCallNoLookup(final String className, final String name, final String desc) {
         return new Call(null, className, name, desc) {
             @Override
-            public MethodEmitter invoke(final MethodEmitter method) {
+            MethodEmitter invoke(final MethodEmitter method) {
                 return method.invokespecial(className, name, descriptor);
             }
+
+            @Override
+            public void invoke(MethodVisitor mv) {
+                mv.visitMethodInsn(Opcodes.INVOKESPECIAL, className, name, desc, false);
+            }
         };
     }
 
@@ -396,9 +403,14 @@
     public static Call staticCallNoLookup(final String className, final String name, final String desc) {
         return new Call(null, className, name, desc) {
             @Override
-            public MethodEmitter invoke(final MethodEmitter method) {
+            MethodEmitter invoke(final MethodEmitter method) {
                 return method.invokestatic(className, name, descriptor);
             }
+
+            @Override
+            public void invoke(MethodVisitor mv) {
+                mv.visitMethodInsn(Opcodes.INVOKESTATIC, className, name, desc, false);
+            }
         };
     }
 
@@ -431,9 +443,14 @@
     public static Call virtualCallNoLookup(final Class<?> clazz, final String name, final Class<?> rtype, final Class<?>... ptypes) {
         return new Call(null, className(clazz), name, methodDescriptor(rtype, ptypes)) {
             @Override
-            public MethodEmitter invoke(final MethodEmitter method) {
+            MethodEmitter invoke(final MethodEmitter method) {
                 return method.invokevirtual(className, name, descriptor);
             }
+
+            @Override
+            public void invoke(MethodVisitor mv) {
+                mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, className, name, descriptor, false);
+            }
         };
     }
 
@@ -451,9 +468,14 @@
     public static Call interfaceCallNoLookup(final Class<?> clazz, final String name, final Class<?> rtype, final Class<?>... ptypes) {
         return new Call(null, className(clazz), name, methodDescriptor(rtype, ptypes)) {
             @Override
-            public MethodEmitter invoke(final MethodEmitter method) {
+            MethodEmitter invoke(final MethodEmitter method) {
                 return method.invokeinterface(className, name, descriptor);
             }
+
+            @Override
+            public void invoke(MethodVisitor mv) {
+                mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, className, name, descriptor, true);
+            }
         };
     }
 
@@ -547,9 +569,14 @@
     public static Call staticCall(final MethodHandles.Lookup lookup, final Class<?> clazz, final String name, final Class<?> rtype, final Class<?>... ptypes) {
         return new Call(MH.findStatic(lookup, clazz, name, MH.type(rtype, ptypes)), className(clazz), name, methodDescriptor(rtype, ptypes)) {
             @Override
-            public MethodEmitter invoke(final MethodEmitter method) {
+            MethodEmitter invoke(final MethodEmitter method) {
                 return method.invokestatic(className, name, descriptor);
             }
+
+            @Override
+            public void invoke(MethodVisitor mv) {
+                mv.visitMethodInsn(Opcodes.INVOKESTATIC, className, name, descriptor, false);
+            }
         };
     }
 
@@ -567,29 +594,13 @@
     public static Call virtualCall(final MethodHandles.Lookup lookup, final Class<?> clazz, final String name, final Class<?> rtype, final Class<?>... ptypes) {
         return new Call(MH.findVirtual(lookup, clazz, name, MH.type(rtype, ptypes)), className(clazz), name, methodDescriptor(rtype, ptypes)) {
             @Override
-            public MethodEmitter invoke(final MethodEmitter method) {
+            MethodEmitter invoke(final MethodEmitter method) {
                 return method.invokevirtual(className, name, descriptor);
             }
-        };
-    }
 
-    /**
-     * Create a special call, given an explicit lookup, looking up the method handle for it at the same time
-     *
-     * @param lookup    the lookup
-     * @param thisClass this class
-     * @param clazz     the class
-     * @param name      the name of the method
-     * @param rtype     the return type
-     * @param ptypes    the parameter types
-     *
-     * @return the call object representing the virtual call
-     */
-    public static Call specialCall0(final MethodHandles.Lookup lookup, final Class<?> thisClass, final Class<?> clazz, final String name, final Class<?> rtype, final Class<?>... ptypes) {
-        return new Call(MH.findSpecial(lookup, clazz, name, MH.type(rtype, ptypes), thisClass), className(clazz), name, methodDescriptor(rtype, ptypes)) {
             @Override
-            public MethodEmitter invoke(final MethodEmitter method) {
-                return method.invokespecial(className, name, descriptor);
+            public void invoke(MethodVisitor mv) {
+                mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, className, name, descriptor, false);
             }
         };
     }
@@ -609,9 +620,14 @@
     public static Call specialCall(final MethodHandles.Lookup lookup, final Class<?> clazz, final String name, final Class<?> rtype, final Class<?>... ptypes) {
         return new Call(MH.findSpecial(lookup, clazz, name, MH.type(rtype, ptypes), clazz), className(clazz), name, methodDescriptor(rtype, ptypes)) {
             @Override
-            public MethodEmitter invoke(final MethodEmitter method) {
+            MethodEmitter invoke(final MethodEmitter method) {
                 return method.invokespecial(className, name, descriptor);
             }
+
+            @Override
+            public void invoke(MethodVisitor mv) {
+                mv.visitMethodInsn(Opcodes.INVOKESPECIAL, className, name, descriptor, false);
+            }
         };
     }
 
@@ -757,7 +773,14 @@
          *
          * @return the method emitter
          */
-        protected abstract MethodEmitter invoke(final MethodEmitter emitter);
+        abstract MethodEmitter invoke(final MethodEmitter emitter);
+
+        /**
+         * Generate invocation code for the method
+         *
+         * @param mv a method visitor
+         */
+        public abstract void invoke(final MethodVisitor mv);
     }
 
 }
--- a/src/jdk/nashorn/internal/codegen/DumpBytecode.java	Wed May 14 10:51:39 2014 +0200
+++ b/src/jdk/nashorn/internal/codegen/DumpBytecode.java	Wed May 14 15:55:27 2014 +0200
@@ -31,12 +31,20 @@
 import java.io.PrintWriter;
 import jdk.nashorn.internal.runtime.ECMAErrors;
 import jdk.nashorn.internal.runtime.ScriptEnvironment;
+import jdk.nashorn.internal.runtime.logging.DebugLogger;
 
 /**
- * Class that facilitates dumping bytecode to disk
+ * Class that facilitates printing bytecode and dumping it to disk.
  */
-final class DumpBytecode {
-    static void dumpBytecode(final ScriptEnvironment env, final Compiler compiler, final byte[] bytecode, final String className) {
+public final class DumpBytecode {
+    /**
+     * Dump bytecode to console and potentially disk.
+     * @param env the script environment defining options for printing bytecode
+     * @param logger a logger used to write diagnostics about bytecode dumping
+     * @param bytecode the actual code to dump
+     * @param className the name of the class being dumped
+     */
+    public static void dumpBytecode(final ScriptEnvironment env, final DebugLogger logger, final byte[] bytecode, final String className) {
         File dir = null;
         try {
             // should could be printed to stderr for generate class?
@@ -98,10 +106,10 @@
                 try (final FileOutputStream fos = new FileOutputStream(file)) {
                     fos.write(bytecode);
                 }
-                compiler.getLogger().info("Wrote class to '" + file.getAbsolutePath() + '\'');
+                logger.info("Wrote class to '" + file.getAbsolutePath() + '\'');
             }
         } catch (final IOException e) {
-            compiler.getLogger().warning("Skipping class dump for ",
+            logger.warning("Skipping class dump for ",
                     className,
                     ": ",
                     ECMAErrors.getMessage(
--- a/src/jdk/nashorn/internal/runtime/linker/JavaAdapterBytecodeGenerator.java	Wed May 14 10:51:39 2014 +0200
+++ b/src/jdk/nashorn/internal/runtime/linker/JavaAdapterBytecodeGenerator.java	Wed May 14 15:55:27 2014 +0200
@@ -56,7 +56,9 @@
 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;
@@ -66,6 +68,7 @@
 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.JSType;
 import jdk.nashorn.internal.runtime.ScriptFunction;
 import jdk.nashorn.internal.runtime.ScriptObject;
 import jdk.nashorn.internal.runtime.linker.AdaptationResult.Outcome;
@@ -132,10 +135,11 @@
  * implemented securely.
  */
 final class JavaAdapterBytecodeGenerator {
-    static final Type CONTEXT_TYPE       = Type.getType(Context.class);
-    static final Type OBJECT_TYPE        = Type.getType(Object.class);
-    static final Type SCRIPT_OBJECT_TYPE = Type.getType(ScriptObject.class);
-    static final Type GLOBAL_TYPE        = Type.getType(Global.class);
+    private static final Type CONTEXT_TYPE       = Type.getType(Context.class);
+    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 GLOBAL_TYPE        = Type.getType(Global.class);
+    private static final Type CLASS_TYPE         = Type.getType(Class.class);
 
     static final String CONTEXT_TYPE_NAME = CONTEXT_TYPE.getInternalName();
     static final String OBJECT_TYPE_NAME  = OBJECT_TYPE.getInternalName();
@@ -172,7 +176,11 @@
 
     private static final String METHOD_HANDLE_TYPE_DESCRIPTOR = METHOD_HANDLE_TYPE.getDescriptor();
     private static final String GET_GLOBAL_METHOD_DESCRIPTOR = Type.getMethodDescriptor(GLOBAL_TYPE);
-    private static final String GET_CLASS_METHOD_DESCRIPTOR = Type.getMethodDescriptor(Type.getType(Class.class));
+    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 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);
 
     // 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.
@@ -183,6 +191,7 @@
     private static final int MAX_GENERATED_TYPE_NAME_LENGTH = 255;
 
     private static final String CLASS_INIT = "<clinit>";
+    static final String CONVERTER_INIT = "<converter-init>";
 
     // Method name prefix for invoking super-methods
     static final String SUPER_PREFIX = "super$";
@@ -212,6 +221,22 @@
     private boolean autoConvertibleFromFunction = false;
     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 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 Set<Class<?>> samReturnTypes = new HashSet<>();
+
     private final ClassWriter cw;
 
     /**
@@ -249,6 +274,7 @@
         gatherMethods(interfaces);
         samName = abstractMethodNames.size() == 1 ? abstractMethodNames.iterator().next() : null;
         generateHandleFields();
+        generateConverterFields();
         if(classOverride) {
             generateClassInit();
         }
@@ -321,6 +347,24 @@
         }
     }
 
+    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();
+                }
+            }
+        }
+    }
+
     private void generateClassInit() {
         final InstructionAdapter mv = new InstructionAdapter(cw.visitMethod(ACC_STATIC, CLASS_INIT,
                 Type.getMethodDescriptor(Type.VOID_TYPE), null, null));
@@ -340,8 +384,7 @@
             for (final MethodInfo mi : methodInfos) {
                 if(mi.getName().equals(samName)) {
                     mv.dup();
-                    mv.aconst(Type.getMethodType(mi.type.toMethodDescriptorString()));
-                    mv.invokestatic(SERVICES_CLASS_TYPE_NAME, "getHandle", GET_HANDLE_FUNCTION_DESCRIPTOR, false);
+                    loadMethodTypeAndGetHandle(mv, mi, GET_HANDLE_FUNCTION_DESCRIPTOR);
                 } else {
                     mv.visitInsn(ACONST_NULL);
                 }
@@ -357,8 +400,7 @@
         for (final MethodInfo mi : methodInfos) {
             mv.dup();
             mv.aconst(mi.getName());
-            mv.aconst(Type.getMethodType(mi.type.toMethodDescriptorString()));
-            mv.invokestatic(SERVICES_CLASS_TYPE_NAME, "getHandle", GET_HANDLE_OBJECT_DESCRIPTOR, false);
+            loadMethodTypeAndGetHandle(mv, mi, GET_HANDLE_OBJECT_DESCRIPTOR);
             mv.putstatic(generatedClassName, mi.methodHandleFieldName, METHOD_HANDLE_TYPE_DESCRIPTOR);
         }
 
@@ -369,9 +411,41 @@
         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(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();
@@ -503,8 +577,7 @@
                 if(!fromFunction) {
                     mv.aconst(mi.getName());
                 }
-                mv.aconst(Type.getMethodType(mi.type.toMethodDescriptorString()));
-                mv.invokestatic(SERVICES_CLASS_TYPE_NAME, "getHandle", getHandleDescriptor, false);
+                loadMethodTypeAndGetHandle(mv, mi, getHandleDescriptor);
             }
             mv.putfield(generatedClassName, mi.methodHandleFieldName, METHOD_HANDLE_TYPE_DESCRIPTOR);
         }
@@ -514,6 +587,8 @@
         invokeGetGlobalWithNullCheck(mv);
         mv.putfield(generatedClassName, GLOBAL_FIELD_NAME, GLOBAL_TYPE_DESCRIPTOR);
 
+        // Initialize converters
+        generateConverterInit(mv, fromFunction);
         endInitMethod(mv);
     }
 
@@ -628,7 +703,8 @@
 
         final Label handleDefined = new Label();
 
-        final Type asmReturnType = Type.getType(type.returnType());
+        final Class<?> returnType = type.returnType();
+        final Type asmReturnType = Type.getType(returnType);
 
         // See if we have overriding method handle defined
         if(classOverride) {
@@ -638,7 +714,8 @@
             mv.getfield(generatedClassName, mi.methodHandleFieldName, METHOD_HANDLE_TYPE_DESCRIPTOR);
         }
         // stack: [handle]
-        jumpIfNonNullKeepOperand(mv, handleDefined);
+        mv.visitInsn(DUP);
+        mv.visitJumpInsn(IFNONNULL, handleDefined);
 
         // No handle is available, fall back to default behavior
         if(Modifier.isAbstract(method.getModifiers())) {
@@ -648,6 +725,7 @@
             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);
         }
@@ -709,17 +787,20 @@
         mv.visitVarInsn(ISTORE, globalsDifferVar);
         // stack: [handle]
 
-        // Load all parameters back on stack for dynamic invocation.
+        // 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.
         int varOffset = 1;
         for (final Type t : asmArgTypes) {
             mv.load(varOffset, t);
+            boxStackTop(mv, t);
             varOffset += t.getSize();
         }
 
         // Invoke the target method handle
         final Label tryBlockStart = new Label();
         mv.visitLabel(tryBlockStart);
-        mv.invokevirtual(METHOD_HANDLE_TYPE.getInternalName(), "invokeExact", type.toMethodDescriptorString(), false);
+        emitInvokeExact(mv, type.generic());
+        convertReturnValue(mv, returnType, asmReturnType);
         final Label tryBlockEnd = new Label();
         mv.visitLabel(tryBlockEnd);
         emitFinally(mv, currentGlobalVar, globalsDifferVar);
@@ -749,7 +830,7 @@
         mv.visitLabel(methodEnd);
 
         mv.visitLocalVariable("currentGlobal", GLOBAL_TYPE_DESCRIPTOR, null, setupGlobal, methodEnd, currentGlobalVar);
-        mv.visitLocalVariable("globalsDiffer", Type.INT_TYPE.getDescriptor(), null, setupGlobal, methodEnd, globalsDifferVar);
+        mv.visitLocalVariable("globalsDiffer", Type.BOOLEAN_TYPE.getDescriptor(), null, setupGlobal, methodEnd, globalsDifferVar);
 
         if(throwableDeclared) {
             mv.visitTryCatchBlock(tryBlockStart, tryBlockEnd, rethrowHandler, THROWABLE_TYPE_NAME);
@@ -765,16 +846,106 @@
         endMethod(mv);
     }
 
-    /**
-     * Emits code for jumping to a label if the top stack operand is not null. The operand is kept on the stack if it
-     * is not null (so is available to code at the jump address) and is popped if it is null.
-     * @param mv the instruction adapter being used to emit code
-     * @param label the label to jump to
-     */
-    private static void jumpIfNonNullKeepOperand(final InstructionAdapter mv, final Label label) {
-        mv.visitInsn(DUP);
-        mv.visitJumpInsn(IFNONNULL, label);
-        mv.visitInsn(POP);
+    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 void emitInvokeExact(final InstructionAdapter mv, final MethodType type) {
+        mv.invokevirtual(METHOD_HANDLE_TYPE.getInternalName(), "invokeExact", type.toMethodDescriptorString(), false);
+    }
+
+    private static void boxStackTop(final InstructionAdapter mv, final Type t) {
+        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');
+            break;
+        case Type.FLOAT:
+            // floats get boxed as doubles
+            mv.visitInsn(Opcodes.F2D);
+            invokeValueOf(mv, "Double", 'D');
+            break;
+        case Type.LONG:
+            invokeValueOf(mv, "Long", 'J');
+            break;
+        case Type.DOUBLE:
+            invokeValueOf(mv, "Double", 'D');
+            break;
+        case Type.ARRAY:
+        case Type.OBJECT:
+        case Type.METHOD:
+            // Already boxed
+            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);
     }
 
     /**
--- a/src/jdk/nashorn/internal/runtime/linker/JavaAdapterClassLoader.java	Wed May 14 10:51:39 2014 +0200
+++ b/src/jdk/nashorn/internal/runtime/linker/JavaAdapterClassLoader.java	Wed May 14 15:55:27 2014 +0200
@@ -31,6 +31,8 @@
 import java.security.ProtectionDomain;
 import java.security.SecureClassLoader;
 import jdk.internal.dynalink.beans.StaticClass;
+import jdk.nashorn.internal.codegen.DumpBytecode;
+import jdk.nashorn.internal.runtime.Context;
 
 /**
  * This class encapsulates the bytecode of the adapter class and can be used to load it into the JVM as an actual Class.
@@ -41,6 +43,7 @@
  */
 final class JavaAdapterClassLoader {
     private static final AccessControlContext CREATE_LOADER_ACC_CTXT = ClassAndLoader.createPermAccCtxt("createClassLoader");
+    private static final AccessControlContext GET_CONTEXT_ACC_CTXT = ClassAndLoader.createPermAccCtxt(Context.NASHORN_GET_CONTEXT);
 
     private final String className;
     private final byte[] classBytes;
@@ -101,6 +104,14 @@
             protected Class<?> findClass(final String name) throws ClassNotFoundException {
                 if(name.equals(className)) {
                     assert classBytes != null : "what? already cleared .class bytes!!";
+
+                    final Context ctx = AccessController.doPrivileged(new PrivilegedAction<Context>() {
+                        @Override
+                        public Context run() {
+                            return Context.getContext();
+                        }
+                    }, GET_CONTEXT_ACC_CTXT);
+                    DumpBytecode.dumpBytecode(ctx.getEnv(), ctx.getLogger(jdk.nashorn.internal.codegen.Compiler.class), classBytes, name);
                     return defineClass(name, classBytes, 0, classBytes.length, protectionDomain);
                 }
                 throw new ClassNotFoundException(name);
--- a/src/jdk/nashorn/internal/runtime/linker/JavaAdapterServices.java	Wed May 14 10:51:39 2014 +0200
+++ b/src/jdk/nashorn/internal/runtime/linker/JavaAdapterServices.java	Wed May 14 15:55:27 2014 +0200
@@ -185,4 +185,45 @@
             throw new AssertionError(e.getMessage(), e);
         }
     }
+
+    /**
+     * 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}.
+     * @param obj the return value
+     * @return the filtered return value.
+     */
+    public static Object exportReturnValue(final Object obj) {
+        return NashornBeansLinker.exportArgument(obj);
+    }
+
+    /**
+     * Invoked to convert a return value of a delegate function to primitive char. There's no suitable conversion in
+     * {@code JSType}, so we provide our own to adapters.
+     * @param obj the return value.
+     * @return the character value of the return value
+     */
+    public static char toCharPrimitive(final Object obj) {
+        return JavaArgumentConverters.toCharPrimitive(obj);
+    }
+
+    /**
+     * 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
+     */
+    public static String toString(final Object obj) {
+        return JavaArgumentConverters.toString(obj);
+    }
 }
--- a/src/jdk/nashorn/internal/runtime/linker/JavaArgumentConverters.java	Wed May 14 10:51:39 2014 +0200
+++ b/src/jdk/nashorn/internal/runtime/linker/JavaArgumentConverters.java	Wed May 14 15:55:27 2014 +0200
@@ -124,34 +124,14 @@
         return s.charAt(0);
     }
 
-    @SuppressWarnings("unused")
-    private static char toCharPrimitive(final Object obj0) {
+    static char toCharPrimitive(final Object obj0) {
         final Character c = toChar(obj0);
         return c == null ? (char)0 : c;
     }
 
-    // Almost identical to ScriptRuntime.toString, but doesn't handle StaticClass specially, and it returns null for
-    // null instead of the string "null".
-    private static String toString(final Object obj0) {
-        for (Object obj = obj0; ;) {
-            if (obj == null) {
-                return null;
-            } else if (obj instanceof String) {
-                return (String) obj;
-            } else if (obj instanceof ConsString) {
-                return obj.toString();
-            } else if (obj instanceof Number) {
-                return JSType.toString(((Number)obj).doubleValue());
-            } else if (obj instanceof Boolean) {
-                return ((Boolean) obj).toString();
-            } else if (obj == UNDEFINED) {
-                return "undefined";
-            } else if (obj instanceof ScriptObject) {
-                obj = JSType.toPrimitive(obj, String.class);
-                continue;
-            }
-            throw assertUnexpectedType(obj);
-        }
+    // Almost identical to ScriptRuntime.toString, but returns null for null instead of the string "null".
+    static String toString(final Object obj) {
+        return obj == null ? null : JSType.toString(obj);
     }
 
     @SuppressWarnings("unused")
--- a/src/jdk/nashorn/internal/runtime/linker/NashornBeansLinker.java	Wed May 14 10:51:39 2014 +0200
+++ b/src/jdk/nashorn/internal/runtime/linker/NashornBeansLinker.java	Wed May 14 15:55:27 2014 +0200
@@ -67,8 +67,7 @@
         return delegateLinker.getGuardedInvocation(linkRequest, new NashornBeansLinkerServices(linkerServices));
     }
 
-    @SuppressWarnings("unused")
-    private static Object exportArgument(final Object arg) {
+    static Object exportArgument(final Object arg) {
         return arg instanceof ConsString ? arg.toString() : arg;
     }
 
--- a/src/jdk/nashorn/internal/runtime/linker/NashornLinker.java	Wed May 14 10:51:39 2014 +0200
+++ b/src/jdk/nashorn/internal/runtime/linker/NashornLinker.java	Wed May 14 15:55:27 2014 +0200
@@ -188,7 +188,7 @@
      */
     private static GuardedInvocation getArrayConverter(final Class<?> sourceType, final Class<?> targetType) {
         final boolean isSourceTypeNativeArray = sourceType == NativeArray.class;
-        // If source type is more generic than ScriptFunction class, we'll need to use a guard
+        // If source type is more generic than NativeArray class, we'll need to use a guard
         final boolean isSourceTypeGeneric = !isSourceTypeNativeArray && sourceType.isAssignableFrom(NativeArray.class);
 
         if (isSourceTypeNativeArray || isSourceTypeGeneric) {