blob: f98817c8ee60a5f3bb0d691d1536cbdfbcb1b0b7 [file] [log] [blame]
/*
* Copyright (c) 2010, 2013, 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;
import static jdk.nashorn.internal.lookup.Lookup.MH;
import static jdk.nashorn.internal.runtime.ECMAErrors.typeError;
import static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import jdk.nashorn.internal.runtime.linker.JavaAdapterFactory;
/**
* A container for data needed to instantiate a specific {@link ScriptFunction} at runtime.
* Instances of this class are created during codegen and stored in script classes'
* constants array to reduce function instantiation overhead during runtime.
*/
public abstract class ScriptFunctionData {
/** Name of the function or "" for anonynous functions */
protected final String name;
/** All versions of this function that have been generated to code */
protected final CompiledFunctions code;
private int arity;
private final boolean isStrict;
private final boolean isBuiltin;
private final boolean isConstructor;
private static final MethodHandle NEWFILTER = findOwnMH("newFilter", Object.class, Object.class, Object.class);
private static final MethodHandle BIND_VAR_ARGS = findOwnMH("bindVarArgs", Object[].class, Object[].class, Object[].class);
/**
* Constructor
*
* @param name script function name
* @param arity arity
* @param isStrict is the function strict
* @param isBuiltin is the function built in
* @param isConstructor is the function a constructor
*/
protected ScriptFunctionData(final String name, final int arity, final boolean isStrict, final boolean isBuiltin, final boolean isConstructor) {
this.name = name;
this.arity = arity;
this.code = new CompiledFunctions();
this.isStrict = isStrict;
this.isBuiltin = isBuiltin;
this.isConstructor = isConstructor;
}
final int getArity() {
return arity;
}
/**
* Used from e.g. Native*$Constructors as an explicit call. TODO - make arity immutable and final
* @param arity new arity
*/
void setArity(final int arity) {
this.arity = arity;
}
CompiledFunction bind(final CompiledFunction originalInv, final ScriptFunction fn, final Object self, final Object[] args) {
final MethodHandle boundInvoker = bindInvokeHandle(originalInv.getInvoker(), fn, self, args);
//TODO the boundinvoker.type() could actually be more specific here
if (isConstructor()) {
ensureConstructor(originalInv);
return new CompiledFunction(boundInvoker.type(), boundInvoker, bindConstructHandle(originalInv.getConstructor(), fn, args));
}
return new CompiledFunction(boundInvoker.type(), boundInvoker);
}
/**
* Is this a ScriptFunction generated with strict semantics?
* @return true if strict, false otherwise
*/
public boolean isStrict() {
return isStrict;
}
boolean isBuiltin() {
return isBuiltin;
}
boolean isConstructor() {
return isConstructor;
}
boolean needsCallee() {
// we don't know if we need a callee or not unless we are generated
ensureCodeGenerated();
return code.needsCallee();
}
/**
* Returns true if this is a non-strict, non-built-in function that requires non-primitive this argument
* according to ECMA 10.4.3.
* @return true if this argument must be an object
*/
boolean needsWrappedThis() {
return !isStrict && !isBuiltin;
}
String toSource() {
return "function " + (name == null ? "" : name) + "() { [native code] }";
}
String getName() {
return name;
}
/**
* Get this function as a String containing its source code. If no source code
* exists in this ScriptFunction, its contents will be displayed as {@code [native code]}
*
* @return string representation of this function
*/
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
sb.append("name='").
append(name.isEmpty() ? "<anonymous>" : name).
append("' ").
append(code.size()).
append(" invokers=").
append(code);
return sb.toString();
}
/**
* Pick the best invoker, i.e. the one version of this method with as narrow and specific
* types as possible. If the call site arguments are objects, but boxed primitives we can
* also try to get a primitive version of the method and do an unboxing filter, but then
* we need to insert a guard that checks the argument is really always a boxed primitive
* and not suddenly a "real" object
*
* @param callSiteType callsite type
* @param args arguments at callsite on first trampoline invocation
* @return method handle to best invoker
*/
MethodHandle getBestInvoker(final MethodType callSiteType, final Object[] args) {
return getBest(callSiteType).getInvoker();
}
MethodHandle getBestInvoker(final MethodType callSiteType) {
return getBestInvoker(callSiteType, null);
}
MethodHandle getBestConstructor(final MethodType callSiteType, final Object[] args) {
if (!isConstructor()) {
throw typeError("not.a.constructor", toSource());
}
ensureCodeGenerated();
final CompiledFunction best = getBest(callSiteType);
ensureConstructor(best);
return best.getConstructor();
}
MethodHandle getBestConstructor(final MethodType callSiteType) {
return getBestConstructor(callSiteType, null);
}
/**
* Subclass responsibility. If we can have lazy code generation, this is a hook to ensure that
* code exists before performing an operation.
*/
protected void ensureCodeGenerated() {
//empty
}
/**
* Return a generic Object/Object invoker for this method. It will ensure code
* is generated, get the most generic of all versions of this function and adapt it
* to Objects.
*
* TODO this is only public because {@link JavaAdapterFactory} can't supply us with
* a MethodType that we can use for lookup due to boostrapping problems. Can be fixed
*
* @return generic invoker of this script function
*/
public final MethodHandle getGenericInvoker() {
ensureCodeGenerated();
return composeGenericMethod(code.mostGeneric().getInvoker());
}
private CompiledFunction getBest(final MethodType callSiteType) {
ensureCodeGenerated();
return code.best(callSiteType);
}
/**
* Allocates an object using this function's allocator.
* @return the object allocated using this function's allocator, or null if the function doesn't have an allocator.
*/
ScriptObject allocate() {
return null;
}
/**
* This method is used to create the immutable portion of a bound function.
* See {@link ScriptFunction#makeBoundFunction(Object, Object[])}
*
* @param fn the original function being bound
* @param self this reference to bind. Can be null.
* @param args additional arguments to bind. Can be null.
*/
ScriptFunctionData makeBoundFunctionData(final ScriptFunction fn, final Object self, final Object[] args) {
ensureCodeGenerated();
final Object[] allArgs = args == null ? ScriptRuntime.EMPTY_ARRAY : args;
final int length = args == null ? 0 : args.length;
CompiledFunctions boundList = new CompiledFunctions();
for (final CompiledFunction inv : code) {
boundList.add(bind(inv, fn, self, allArgs));
}
ScriptFunctionData boundData = new FinalScriptFunctionData(name, arity == -1 ? -1 : Math.max(0, arity - length), boundList, isStrict(), isBuiltin(), isConstructor());
return boundData;
}
/**
* Compose a constructor given a primordial constructor handle
*
* @param ctor primordial constructor handle
* @param needsCallee do we need to pass a callee
*
* @return the composed constructor
*/
protected MethodHandle composeConstructor(final MethodHandle ctor, final boolean needsCallee) {
// If it was (callee, this, args...), permute it to (this, callee, args...). We're doing this because having
// "this" in the first argument position is what allows the elegant folded composition of
// (newFilter x constructor x allocator) further down below in the code. Also, ensure the composite constructor
// always returns Object.
MethodHandle composedCtor = needsCallee ? swapCalleeAndThis(ctor) : ctor;
composedCtor = changeReturnTypeToObject(composedCtor);
final MethodType ctorType = composedCtor.type();
// Construct a dropping type list for NEWFILTER, but don't include constructor "this" into it, so it's actually
// captured as "allocation" parameter of NEWFILTER after we fold the constructor into it.
// (this, [callee, ]args...) => ([callee, ]args...)
final Class<?>[] ctorArgs = ctorType.dropParameterTypes(0, 1).parameterArray();
// Fold constructor into newFilter that replaces the return value from the constructor with the originally
// allocated value when the originally allocated value is a primitive.
// (result, this, [callee, ]args...) x (this, [callee, ]args...) => (this, [callee, ]args...)
composedCtor = MH.foldArguments(MH.dropArguments(NEWFILTER, 2, ctorArgs), composedCtor);
// allocate() takes a ScriptFunction and returns a newly allocated ScriptObject...
if (needsCallee) {
// ...we either fold it into the previous composition, if we need both the ScriptFunction callee object and
// the newly allocated object in the arguments, so (this, callee, args...) x (callee) => (callee, args...),
// or...
return MH.foldArguments(composedCtor, ScriptFunction.ALLOCATE);
}
// ...replace the ScriptFunction argument with the newly allocated object, if it doesn't need the callee
// (this, args...) filter (callee) => (callee, args...)
return MH.filterArguments(composedCtor, 0, ScriptFunction.ALLOCATE);
}
/**
* If this function's method handles need a callee parameter, swap the order of first two arguments for the passed
* method handle. If this function's method handles don't need a callee parameter, returns the original method
* handle unchanged.
*
* @param mh a method handle with order of arguments {@code (callee, this, args...)}
*
* @return a method handle with order of arguments {@code (this, callee, args...)}
*/
private static MethodHandle swapCalleeAndThis(final MethodHandle mh) {
final MethodType type = mh.type();
assert type.parameterType(0) == ScriptFunction.class : type;
assert type.parameterType(1) == Object.class : type;
final MethodType newType = type.changeParameterType(0, Object.class).changeParameterType(1, ScriptFunction.class);
final int[] reorder = new int[type.parameterCount()];
reorder[0] = 1;
assert reorder[1] == 0;
for (int i = 2; i < reorder.length; ++i) {
reorder[i] = i;
}
return MethodHandles.permuteArguments(mh, newType, reorder);
}
/**
* Convert this argument for non-strict functions according to ES 10.4.3
*
* @param thiz the this argument
*
* @return the converted this object
*/
private Object convertThisObject(final Object thiz) {
if (!(thiz instanceof ScriptObject) && needsWrappedThis()) {
if (JSType.nullOrUndefined(thiz)) {
return Context.getGlobalTrusted();
}
if (isPrimitiveThis(thiz)) {
return ((GlobalObject)Context.getGlobalTrusted()).wrapAsObject(thiz);
}
}
return thiz;
}
static boolean isPrimitiveThis(final Object obj) {
return obj instanceof String || obj instanceof ConsString ||
obj instanceof Number || obj instanceof Boolean;
}
/**
* Creates an invoker method handle for a bound function.
*
* @param targetFn the function being bound
* @param originalInvoker an original invoker method handle for the function. This can be its generic invoker or
* any of its specializations.
* @param self the "this" value being bound
* @param args additional arguments being bound
*
* @return a bound invoker method handle that will bind the self value and the specified arguments. The resulting
* invoker never needs a callee; if the original invoker needed it, it will be bound to {@code fn}. The resulting
* invoker still takes an initial {@code this} parameter, but it is always dropped and the bound {@code self} passed
* to the original invoker on invocation.
*/
private MethodHandle bindInvokeHandle(final MethodHandle originalInvoker, final ScriptFunction targetFn, final Object self, final Object[] args) {
// Is the target already bound? If it is, we won't bother binding either callee or self as they're already bound
// in the target and will be ignored anyway.
final boolean isTargetBound = targetFn.isBoundFunction();
final boolean needsCallee = needsCallee(originalInvoker);
assert needsCallee == needsCallee() : "callee contract violation 2";
assert !(isTargetBound && needsCallee); // already bound functions don't need a callee
final Object boundSelf = isTargetBound ? null : convertThisObject(self);
final MethodHandle boundInvoker;
if (isVarArg(originalInvoker)) {
// First, bind callee and this without arguments
final MethodHandle noArgBoundInvoker;
if (isTargetBound) {
// Don't bind either callee or this
noArgBoundInvoker = originalInvoker;
} else if (needsCallee) {
// Bind callee and this
noArgBoundInvoker = MH.insertArguments(originalInvoker, 0, targetFn, boundSelf);
} else {
// Only bind this
noArgBoundInvoker = MH.bindTo(originalInvoker, boundSelf);
}
// Now bind arguments
if (args.length > 0) {
boundInvoker = varArgBinder(noArgBoundInvoker, args);
} else {
boundInvoker = noArgBoundInvoker;
}
} else {
// If target is already bound, insert additional bound arguments after "this" argument, at position 1.
final int argInsertPos = isTargetBound ? 1 : 0;
final Object[] boundArgs = new Object[Math.min(originalInvoker.type().parameterCount() - argInsertPos, args.length + (isTargetBound ? 0 : (needsCallee ? 2 : 1)))];
int next = 0;
if (!isTargetBound) {
if (needsCallee) {
boundArgs[next++] = targetFn;
}
boundArgs[next++] = boundSelf;
}
// If more bound args were specified than the function can take, we'll just drop those.
System.arraycopy(args, 0, boundArgs, next, boundArgs.length - next);
// If target is already bound, insert additional bound arguments after "this" argument, at position 1;
// "this" will get dropped anyway by the target invoker. We previously asserted that already bound functions
// don't take a callee parameter, so we can know that the signature is (this[, args...]) therefore args
// start at position 1. If the function is not bound, we start inserting arguments at position 0.
boundInvoker = MH.insertArguments(originalInvoker, argInsertPos, boundArgs);
}
if (isTargetBound) {
return boundInvoker;
}
// If the target is not already bound, add a dropArguments that'll throw away the passed this
return MH.dropArguments(boundInvoker, 0, Object.class);
}
/**
* Creates a constructor method handle for a bound function using the passed constructor handle.
*
* @param originalConstructor the constructor handle to bind. It must be a composed constructor.
* @param fn the function being bound
* @param args arguments being bound
*
* @return a bound constructor method handle that will bind the specified arguments. The resulting constructor never
* needs a callee; if the original constructor needed it, it will be bound to {@code fn}. The resulting constructor
* still takes an initial {@code this} parameter and passes it to the underlying original constructor. Finally, if
* this script function data object has no constructor handle, null is returned.
*/
private static MethodHandle bindConstructHandle(final MethodHandle originalConstructor, final ScriptFunction fn, final Object[] args) {
assert originalConstructor != null;
// If target function is already bound, don't bother binding the callee.
final MethodHandle calleeBoundConstructor = fn.isBoundFunction() ? originalConstructor :
MH.dropArguments(MH.bindTo(originalConstructor, fn), 0, ScriptFunction.class);
if (args.length == 0) {
return calleeBoundConstructor;
}
if (isVarArg(calleeBoundConstructor)) {
return varArgBinder(calleeBoundConstructor, args);
}
final Object[] boundArgs;
final int maxArgCount = calleeBoundConstructor.type().parameterCount() - 1;
if (args.length <= maxArgCount) {
boundArgs = args;
} else {
boundArgs = new Object[maxArgCount];
System.arraycopy(args, 0, boundArgs, 0, maxArgCount);
}
return MH.insertArguments(calleeBoundConstructor, 1, boundArgs);
}
/**
* Takes a method handle, and returns a potentially different method handle that can be used in
* {@code ScriptFunction#invoke(Object, Object...)} or {code ScriptFunction#construct(Object, Object...)}.
* The returned method handle will be sure to return {@code Object}, and will have all its parameters turned into
* {@code Object} as well, except for the following ones:
* <ul>
* <li>a last parameter of type {@code Object[]} which is used for vararg functions,</li>
* <li>the first argument, which is forced to be {@link ScriptFunction}, in case the function receives itself
* (callee) as an argument.</li>
* </ul>
*
* @param mh the original method handle
*
* @return the new handle, conforming to the rules above.
*/
protected MethodHandle composeGenericMethod(final MethodHandle mh) {
final MethodType type = mh.type();
MethodType newType = type.generic();
if (isVarArg(mh)) {
newType = newType.changeParameterType(type.parameterCount() - 1, Object[].class);
}
if (needsCallee(mh)) {
newType = newType.changeParameterType(0, ScriptFunction.class);
}
return type.equals(newType) ? mh : mh.asType(newType);
}
/**
* Execute this script function.
*
* @param self Target object.
* @param arguments Call arguments.
* @return ScriptFunction result.
*
* @throws Throwable if there is an exception/error with the invocation or thrown from it
*/
Object invoke(final ScriptFunction fn, final Object self, final Object... arguments) throws Throwable {
final MethodHandle mh = getGenericInvoker();
final Object selfObj = convertThisObject(self);
final Object[] args = arguments == null ? ScriptRuntime.EMPTY_ARRAY : arguments;
if (isVarArg(mh)) {
if (needsCallee(mh)) {
return mh.invokeExact(fn, selfObj, args);
}
return mh.invokeExact(selfObj, args);
}
final int paramCount = mh.type().parameterCount();
if (needsCallee(mh)) {
switch (paramCount) {
case 2:
return mh.invokeExact(fn, selfObj);
case 3:
return mh.invokeExact(fn, selfObj, getArg(args, 0));
case 4:
return mh.invokeExact(fn, selfObj, getArg(args, 0), getArg(args, 1));
case 5:
return mh.invokeExact(fn, selfObj, getArg(args, 0), getArg(args, 1), getArg(args, 2));
default:
return mh.invokeWithArguments(withArguments(fn, selfObj, paramCount, args));
}
}
switch (paramCount) {
case 1:
return mh.invokeExact(selfObj);
case 2:
return mh.invokeExact(selfObj, getArg(args, 0));
case 3:
return mh.invokeExact(selfObj, getArg(args, 0), getArg(args, 1));
case 4:
return mh.invokeExact(selfObj, getArg(args, 0), getArg(args, 1), getArg(args, 2));
default:
return mh.invokeWithArguments(withArguments(null, selfObj, paramCount, args));
}
}
private static Object getArg(final Object[] args, final int i) {
return i < args.length ? args[i] : UNDEFINED;
}
private static Object[] withArguments(final ScriptFunction fn, final Object self, final int argCount, final Object[] args) {
final Object[] finalArgs = new Object[argCount];
int nextArg = 0;
if (fn != null) {
//needs callee
finalArgs[nextArg++] = fn;
}
finalArgs[nextArg++] = self;
// Don't add more args that there is argCount in the handle (including self and callee).
for (int i = 0; i < args.length && nextArg < argCount;) {
finalArgs[nextArg++] = args[i++];
}
// If we have fewer args than argCount, pad with undefined.
while (nextArg < argCount) {
finalArgs[nextArg++] = UNDEFINED;
}
return finalArgs;
}
/**
* Takes a variable-arity method and binds a variable number of arguments in it. The returned method will filter the
* vararg array and pass a different array that prepends the bound arguments in front of the arguments passed on
* invocation
*
* @param mh the handle
* @param args the bound arguments
*
* @return the bound method handle
*/
private static MethodHandle varArgBinder(final MethodHandle mh, final Object[] args) {
assert args != null;
assert args.length > 0;
return MH.filterArguments(mh, mh.type().parameterCount() - 1, MH.bindTo(BIND_VAR_ARGS, args));
}
/**
* Adapts the method handle so its return type is {@code Object}. If the handle's return type is already
* {@code Object}, the handle is returned unchanged.
*
* @param mh the handle to adapt
* @return the adapted handle
*/
private static MethodHandle changeReturnTypeToObject(final MethodHandle mh) {
return MH.asType(mh, mh.type().changeReturnType(Object.class));
}
private void ensureConstructor(final CompiledFunction inv) {
if (!inv.hasConstructor()) {
inv.setConstructor(composeConstructor(inv.getInvoker(), needsCallee(inv.getInvoker())));
}
}
/**
* Heuristic to figure out if the method handle has a callee argument. If it's type is either
* {@code (boolean, Object, ScriptFunction, ...)} or {@code (Object, ScriptFunction, ...)}, then we'll assume it has
* a callee argument. We need this as the constructor above is not passed this information, and can't just blindly
* assume it's false (notably, it's being invoked for creation of new scripts, and scripts have scopes, therefore
* they also always receive a callee).
*
* @param mh the examined method handle
*
* @return true if the method handle expects a callee, false otherwise
*/
protected static boolean needsCallee(final MethodHandle mh) {
final MethodType type = mh.type();
final int length = type.parameterCount();
if (length == 0) {
return false;
}
if (type.parameterType(0) == boolean.class) {
return length > 1 && type.parameterType(1) == ScriptFunction.class;
}
return type.parameterType(0) == ScriptFunction.class;
}
/**
* Check if a javascript function methodhandle is a vararg handle
*
* @param mh method handle to check
*
* @return true if vararg
*/
protected static boolean isVarArg(final MethodHandle mh) {
final MethodType type = mh.type();
return type.parameterType(type.parameterCount() - 1).isArray();
}
@SuppressWarnings("unused")
private static Object[] bindVarArgs(final Object[] array1, final Object[] array2) {
if (array2 == null) {
// Must clone it, as we can't allow the receiving method to alter the array
return array1.clone();
}
final int l2 = array2.length;
if (l2 == 0) {
return array1.clone();
}
final int l1 = array1.length;
final Object[] concat = new Object[l1 + l2];
System.arraycopy(array1, 0, concat, 0, l1);
System.arraycopy(array2, 0, concat, l1, l2);
return concat;
}
@SuppressWarnings("unused")
private static Object newFilter(final Object result, final Object allocation) {
return (result instanceof ScriptObject || !JSType.isPrimitive(result))? result : allocation;
}
private static MethodHandle findOwnMH(final String name, final Class<?> rtype, final Class<?>... types) {
return MH.findStatic(MethodHandles.lookup(), ScriptFunctionData.class, name, MH.type(rtype, types));
}
}