8043004: Reduce variability at JavaAdapter call sites
Reviewed-by: lagergren, sundar
--- a/nashorn/src/jdk/nashorn/internal/codegen/CompilationPhase.java Wed May 14 10:51:39 2014 +0200
+++ b/nashorn/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/nashorn/src/jdk/nashorn/internal/codegen/CompilerConstants.java Wed May 14 10:51:39 2014 +0200
+++ b/nashorn/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/nashorn/src/jdk/nashorn/internal/codegen/DumpBytecode.java Wed May 14 10:51:39 2014 +0200
+++ b/nashorn/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/nashorn/src/jdk/nashorn/internal/runtime/linker/JavaAdapterBytecodeGenerator.java Wed May 14 10:51:39 2014 +0200
+++ b/nashorn/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/nashorn/src/jdk/nashorn/internal/runtime/linker/JavaAdapterClassLoader.java Wed May 14 10:51:39 2014 +0200
+++ b/nashorn/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/nashorn/src/jdk/nashorn/internal/runtime/linker/JavaAdapterServices.java Wed May 14 10:51:39 2014 +0200
+++ b/nashorn/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/nashorn/src/jdk/nashorn/internal/runtime/linker/JavaArgumentConverters.java Wed May 14 10:51:39 2014 +0200
+++ b/nashorn/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/nashorn/src/jdk/nashorn/internal/runtime/linker/NashornBeansLinker.java Wed May 14 10:51:39 2014 +0200
+++ b/nashorn/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/nashorn/src/jdk/nashorn/internal/runtime/linker/NashornLinker.java Wed May 14 10:51:39 2014 +0200
+++ b/nashorn/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) {