diff options
Diffstat (limited to 'src/jdk/nashorn/internal/runtime/ScriptFunction.java')
-rw-r--r-- | src/jdk/nashorn/internal/runtime/ScriptFunction.java | 518 |
1 files changed, 437 insertions, 81 deletions
diff --git a/src/jdk/nashorn/internal/runtime/ScriptFunction.java b/src/jdk/nashorn/internal/runtime/ScriptFunction.java index 3d8b4ff2..d999c118 100644 --- a/src/jdk/nashorn/internal/runtime/ScriptFunction.java +++ b/src/jdk/nashorn/internal/runtime/ScriptFunction.java @@ -29,18 +29,30 @@ import static jdk.nashorn.internal.codegen.CompilerConstants.virtualCallNoLookup 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 static jdk.nashorn.internal.runtime.UnwarrantedOptimismException.INVALID_PROGRAM_POINT; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; +import java.lang.invoke.SwitchPoint; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; import jdk.internal.dynalink.CallSiteDescriptor; import jdk.internal.dynalink.linker.GuardedInvocation; import jdk.internal.dynalink.linker.LinkRequest; +import jdk.internal.dynalink.support.Guards; +import jdk.nashorn.internal.codegen.ApplySpecialization; +import jdk.nashorn.internal.codegen.Compiler; import jdk.nashorn.internal.codegen.CompilerConstants.Call; -import jdk.nashorn.internal.lookup.MethodHandleFactory; import jdk.nashorn.internal.objects.Global; +import jdk.nashorn.internal.objects.NativeFunction; +import jdk.nashorn.internal.objects.annotations.SpecializedFunction.LinkLogic; +import jdk.nashorn.internal.runtime.linker.Bootstrap; import jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor; -import jdk.nashorn.internal.runtime.linker.NashornGuards; +import jdk.nashorn.internal.runtime.logging.DebugLogger; /** * Runtime representation of a JavaScript function. @@ -48,35 +60,39 @@ import jdk.nashorn.internal.runtime.linker.NashornGuards; public abstract class ScriptFunction extends ScriptObject { /** Method handle for prototype getter for this ScriptFunction */ - public static final MethodHandle G$PROTOTYPE = findOwnMH("G$prototype", Object.class, Object.class); + public static final MethodHandle G$PROTOTYPE = findOwnMH_S("G$prototype", Object.class, Object.class); /** Method handle for prototype setter for this ScriptFunction */ - public static final MethodHandle S$PROTOTYPE = findOwnMH("S$prototype", void.class, Object.class, Object.class); + public static final MethodHandle S$PROTOTYPE = findOwnMH_S("S$prototype", void.class, Object.class, Object.class); /** Method handle for length getter for this ScriptFunction */ - public static final MethodHandle G$LENGTH = findOwnMH("G$length", int.class, Object.class); + public static final MethodHandle G$LENGTH = findOwnMH_S("G$length", int.class, Object.class); /** Method handle for name getter for this ScriptFunction */ - public static final MethodHandle G$NAME = findOwnMH("G$name", Object.class, Object.class); + public static final MethodHandle G$NAME = findOwnMH_S("G$name", Object.class, Object.class); /** Method handle used for implementing sync() in mozilla_compat */ - public static final MethodHandle INVOKE_SYNC = findOwnMH("invokeSync", Object.class, ScriptFunction.class, Object.class, Object.class, Object[].class); + public static final MethodHandle INVOKE_SYNC = findOwnMH_S("invokeSync", Object.class, ScriptFunction.class, Object.class, Object.class, Object[].class); /** Method handle for allocate function for this ScriptFunction */ - static final MethodHandle ALLOCATE = findOwnMH("allocate", Object.class); + static final MethodHandle ALLOCATE = findOwnMH_V("allocate", Object.class); - private static final MethodHandle WRAPFILTER = findOwnMH("wrapFilter", Object.class, Object.class); + private static final MethodHandle WRAPFILTER = findOwnMH_S("wrapFilter", Object.class, Object.class); - private static final MethodHandle GLOBALFILTER = findOwnMH("globalFilter", Object.class, Object.class); + private static final MethodHandle SCRIPTFUNCTION_GLOBALFILTER = findOwnMH_S("globalFilter", Object.class, Object.class); /** method handle to scope getter for this ScriptFunction */ public static final Call GET_SCOPE = virtualCallNoLookup(ScriptFunction.class, "getScope", ScriptObject.class); - private static final MethodHandle IS_FUNCTION_MH = findOwnMH("isFunctionMH", boolean.class, Object.class, ScriptFunctionData.class); + private static final MethodHandle IS_FUNCTION_MH = findOwnMH_S("isFunctionMH", boolean.class, Object.class, ScriptFunctionData.class); + + private static final MethodHandle IS_APPLY_FUNCTION = findOwnMH_S("isApplyFunction", boolean.class, boolean.class, Object.class, Object.class); - private static final MethodHandle IS_NONSTRICT_FUNCTION = findOwnMH("isNonStrictFunction", boolean.class, Object.class, Object.class, ScriptFunctionData.class); + private static final MethodHandle IS_NONSTRICT_FUNCTION = findOwnMH_S("isNonStrictFunction", boolean.class, Object.class, Object.class, ScriptFunctionData.class); - private static final MethodHandle ADD_ZEROTH_ELEMENT = findOwnMH("addZerothElement", Object[].class, Object[].class, Object.class); + private static final MethodHandle ADD_ZEROTH_ELEMENT = findOwnMH_S("addZerothElement", Object[].class, Object[].class, Object.class); + + private static final MethodHandle WRAP_THIS = MH.findStatic(MethodHandles.lookup(), ScriptFunctionData.class, "wrapThis", MH.type(Object.class, Object.class)); /** The parent scope. */ private final ScriptObject scope; @@ -101,7 +117,7 @@ public abstract class ScriptFunction extends ScriptObject { final MethodHandle methodHandle, final PropertyMap map, final ScriptObject scope, - final MethodHandle[] specs, + final Specialization[] specs, final int flags) { this(new FinalScriptFunctionData(name, methodHandle, specs, flags), map, scope); @@ -195,6 +211,10 @@ public abstract class ScriptFunction extends ScriptObject { return data.needsWrappedThis(); } + private static boolean needsWrappedThis(final Object fn) { + return fn instanceof ScriptFunction ? ((ScriptFunction)fn).needsWrappedThis() : false; + } + /** * Execute this script function. * @param self Target object. @@ -230,12 +250,13 @@ public abstract class ScriptFunction extends ScriptObject { if (Context.DEBUG) { allocations++; } + assert !isBoundFunction(); // allocate never invoked on bound functions final ScriptObject object = data.allocate(allocatorMap); if (object != null) { - Object prototype = getPrototype(); + final Object prototype = getPrototype(); if (prototype instanceof ScriptObject) { object.setInitialProto((ScriptObject)prototype); } @@ -311,26 +332,7 @@ public abstract class ScriptFunction extends ScriptObject { * @param sync the Object to synchronize on, or undefined * @return synchronized function */ - public abstract ScriptFunction makeSynchronizedFunction(Object sync); - - /** - * Return the most appropriate invoke handle if there are specializations - * @param type most specific method type to look for invocation with - * @param args args for trampoline invocation - * @return invoke method handle - */ - private MethodHandle getBestInvoker(final MethodType type, final Object[] args) { - return data.getBestInvoker(type, args); - } - - /** - * Return the most appropriate invoke handle if there are specializations - * @param type most specific method type to look for invocation with - * @return invoke method handle - */ - public MethodHandle getBestInvoker(final MethodType type) { - return getBestInvoker(type, null); - } + public abstract ScriptFunction makeSynchronizedFunction(Object sync); /** * Return the invoke handle bound to a given ScriptObject self reference. @@ -340,7 +342,7 @@ public abstract class ScriptFunction extends ScriptObject { * @return bound invoke handle */ public final MethodHandle getBoundInvokeHandle(final Object self) { - return MH.bindTo(bindToCalleeIfNeeded(data.getGenericInvoker()), self); + return MH.bindTo(bindToCalleeIfNeeded(data.getGenericInvoker(scope)), self); } /** @@ -379,7 +381,7 @@ public abstract class ScriptFunction extends ScriptObject { * @return self's prototype */ public static Object G$prototype(final Object self) { - return (self instanceof ScriptFunction) ? + return self instanceof ScriptFunction ? ((ScriptFunction)self).getPrototype() : UNDEFINED; } @@ -428,9 +430,9 @@ public abstract class ScriptFunction extends ScriptObject { * @param constructor constructor * @return prototype, or null if given constructor is not a ScriptFunction */ - public static ScriptObject getPrototype(final Object constructor) { - if (constructor instanceof ScriptFunction) { - final Object proto = ((ScriptFunction)constructor).getPrototype(); + public static ScriptObject getPrototype(final ScriptFunction constructor) { + if (constructor != null) { + final Object proto = constructor.getPrototype(); if (proto instanceof ScriptObject) { return (ScriptObject)proto; } @@ -466,12 +468,15 @@ public abstract class ScriptFunction extends ScriptObject { } @Override - protected GuardedInvocation findNewMethod(final CallSiteDescriptor desc) { + protected GuardedInvocation findNewMethod(final CallSiteDescriptor desc, final LinkRequest request) { final MethodType type = desc.getMethodType(); - return new GuardedInvocation(pairArguments(data.getBestConstructor(type.changeParameterType(0, ScriptFunction.class), null), type), null, getFunctionGuard(this)); + assert desc.getMethodType().returnType() == Object.class && !NashornCallSiteDescriptor.isOptimistic(desc); + final CompiledFunction cf = data.getBestConstructor(type, scope, CompiledFunction.NO_FUNCTIONS); + final GuardedInvocation bestCtorInv = cf.createConstructorInvocation(); + //TODO - ClassCastException + return new GuardedInvocation(pairArguments(bestCtorInv.getInvocation(), type), getFunctionGuard(this, cf.getFlags()), bestCtorInv.getSwitchPoints(), null); } - @SuppressWarnings("unused") private static Object wrapFilter(final Object obj) { if (obj instanceof ScriptObject || !ScriptFunctionData.isPrimitiveThis(obj)) { return obj; @@ -487,6 +492,35 @@ public abstract class ScriptFunction extends ScriptObject { } /** + * Some receivers are primitive, in that case, according to the Spec we create a new + * native object per callsite with the wrap filter. We can only apply optimistic builtins + * if there is no per instance state saved for these wrapped objects (e.g. currently NativeStrings), + * otherwise we can't create optimistic versions + * + * @param self receiver + * @param linkLogicClass linkLogicClass, or null if no link logic exists + * @return link logic instance, or null if one could not be constructed for this receiver + */ + private static LinkLogic getLinkLogic(final Object self, final Class<? extends LinkLogic> linkLogicClass) { + if (linkLogicClass == null) { + return LinkLogic.EMPTY_INSTANCE; //always OK to link this, specialization but without special linking logic + } + + if (!Context.getContextTrusted().getEnv()._optimistic_types) { + return null; //if optimistic types are off, optimistic builtins are too + } + + final Object wrappedSelf = wrapFilter(self); + if (wrappedSelf instanceof OptimisticBuiltins) { + if (wrappedSelf != self && ((OptimisticBuiltins)wrappedSelf).hasPerInstanceAssumptions()) { + return null; //pessimistic - we created a wrapped object different from the primitive, but the assumptions have instance state + } + return ((OptimisticBuiltins)wrappedSelf).getLinkLogic(linkLogicClass); + } + return null; + } + + /** * dyn:call call site signature: (callee, thiz, [args...]) * generated method signature: (callee, thiz, [args...]) * @@ -497,51 +531,114 @@ public abstract class ScriptFunction extends ScriptObject { * (b) method doesn't have callee parameter (builtin functions) * (3) for local/scope calls, bind thiz and drop both callee and thiz. * (4) for normal this-calls, drop callee. + * + * @return guarded invocation for call */ @Override protected GuardedInvocation findCallMethod(final CallSiteDescriptor desc, final LinkRequest request) { final MethodType type = desc.getMethodType(); - final boolean scopeCall = NashornCallSiteDescriptor.isScope(desc); - if (request.isCallSiteUnstable()) { - // (callee, this, args...) => (callee, this, args[]) - final MethodHandle collector = MH.asCollector(ScriptRuntime.APPLY.methodHandle(), Object[].class, type.parameterCount() - 2); + final String name = getName(); + final boolean isUnstable = request.isCallSiteUnstable(); + final boolean scopeCall = NashornCallSiteDescriptor.isScope(desc); + final boolean isCall = !scopeCall && data.isBuiltin() && "call".equals(name); + final boolean isApply = !scopeCall && data.isBuiltin() && "apply".equals(name); + + final boolean isApplyOrCall = isCall | isApply; + + if (isUnstable && !isApplyOrCall) { + //megamorphic - replace call with apply + final MethodHandle handle; + //ensure that the callsite is vararg so apply can consume it + if (type.parameterCount() == 3 && type.parameterType(2) == Object[].class) { + // Vararg call site + handle = ScriptRuntime.APPLY.methodHandle(); + } else { + // (callee, this, args...) => (callee, this, args[]) + handle = MH.asCollector(ScriptRuntime.APPLY.methodHandle(), Object[].class, type.parameterCount() - 2); + } // If call site is statically typed to take a ScriptFunction, we don't need a guard, otherwise we need a // generic "is this a ScriptFunction?" guard. - return new GuardedInvocation(collector, ScriptFunction.class.isAssignableFrom(desc.getMethodType().parameterType(0)) - ? null : NashornGuards.getScriptFunctionGuard()); + return new GuardedInvocation( + handle, + null, + (SwitchPoint)null, + ClassCastException.class); } MethodHandle boundHandle; MethodHandle guard = null; + // Special handling of Function.apply and Function.call. Note we must be invoking + if (isApplyOrCall && !isUnstable) { + final Object[] args = request.getArguments(); + if (Bootstrap.isCallable(args[1])) { + return createApplyOrCallCall(isApply, desc, request, args); + } + } //else just fall through and link as ordinary function or unstable apply + + int programPoint = INVALID_PROGRAM_POINT; + if (NashornCallSiteDescriptor.isOptimistic(desc)) { + programPoint = NashornCallSiteDescriptor.getProgramPoint(desc); + } + + CompiledFunction cf = data.getBestInvoker(type, scope, CompiledFunction.NO_FUNCTIONS); + final Object self = request.getArguments()[1]; + final Collection<CompiledFunction> forbidden = new HashSet<>(); + + //check for special fast versions of the compiled function + final List<SwitchPoint> sps = new ArrayList<>(); + Class<? extends Throwable> exceptionGuard = null; + + while (cf.isSpecialization()) { + final Class<? extends LinkLogic> linkLogicClass = cf.getLinkLogicClass(); + //if linklogic is null, we can always link with the standard mechanism, it's still a specialization + final LinkLogic linkLogic = getLinkLogic(self, linkLogicClass); + + if (linkLogic != null && linkLogic.checkLinkable(self, desc, request)) { + final DebugLogger log = Context.getContextTrusted().getLogger(Compiler.class); + + if (log.isEnabled()) { + log.info("Linking optimistic builtin function: '", name, "' args=", Arrays.toString(request.getArguments()), " desc=", desc); + } + + exceptionGuard = linkLogic.getRelinkException(); + + break; + } + + //could not link this specialization because link check failed + forbidden.add(cf); + final CompiledFunction oldCf = cf; + cf = data.getBestInvoker(type, scope, forbidden); + assert oldCf != cf; + } + + final GuardedInvocation bestInvoker = cf.createFunctionInvocation(type.returnType(), programPoint); + final MethodHandle callHandle = bestInvoker.getInvocation(); + if (data.needsCallee()) { - final MethodHandle callHandle = getBestInvoker(type, request.getArguments()); if (scopeCall && needsWrappedThis()) { - // Make a handle that drops the passed "this" argument and substitutes either Global or Undefined // (callee, this, args...) => (callee, [this], args...) - boundHandle = MH.filterArguments(callHandle, 1, GLOBALFILTER); + boundHandle = MH.filterArguments(callHandle, 1, SCRIPTFUNCTION_GLOBALFILTER); } else { // It's already (callee, this, args...), just what we need boundHandle = callHandle; } + } else if (data.isBuiltin() && "extend".equals(data.getName())) { + // NOTE: the only built-in named "extend" is NativeJava.extend. As a special-case we're binding the + // current lookup as its "this" so it can do security-sensitive creation of adapter classes. + boundHandle = MH.dropArguments(MH.bindTo(callHandle, desc.getLookup()), 0, type.parameterType(0), type.parameterType(1)); + } else if (scopeCall && needsWrappedThis()) { + // Make a handle that drops the passed "this" argument and substitutes either Global or Undefined + // (this, args...) => ([this], args...) + boundHandle = MH.filterArguments(callHandle, 0, SCRIPTFUNCTION_GLOBALFILTER); + // ([this], args...) => ([callee], [this], args...) + boundHandle = MH.dropArguments(boundHandle, 0, type.parameterType(0)); } else { - final MethodHandle callHandle = getBestInvoker(type.dropParameterTypes(0, 1), request.getArguments()); - if (data.isBuiltin() && "extend".equals(data.getName())) { - // NOTE: the only built-in named "extend" is NativeJava.extend. As a special-case we're binding the - // current lookup as its "this" so it can do security-sensitive creation of adapter classes. - boundHandle = MH.dropArguments(MH.bindTo(callHandle, desc.getLookup()), 0, Object.class, Object.class); - } else if (scopeCall && needsWrappedThis()) { - // Make a handle that drops the passed "this" argument and substitutes either Global or Undefined - // (this, args...) => ([this], args...) - boundHandle = MH.filterArguments(callHandle, 0, GLOBALFILTER); - // ([this], args...) => ([callee], [this], args...) - boundHandle = MH.dropArguments(boundHandle, 0, Object.class); - } else { - // (this, args...) => ([callee], this, args...) - boundHandle = MH.dropArguments(callHandle, 0, Object.class); - } + // (this, args...) => ([callee], this, args...) + boundHandle = MH.dropArguments(callHandle, 0, type.parameterType(0)); } // For non-strict functions, check whether this-object is primitive type. @@ -557,8 +654,257 @@ public abstract class ScriptFunction extends ScriptObject { boundHandle = pairArguments(boundHandle, type); - return new GuardedInvocation(boundHandle, guard == null ? getFunctionGuard(this) : guard); - } + if (bestInvoker.getSwitchPoints() != null) { + sps.addAll(Arrays.asList(bestInvoker.getSwitchPoints())); + } + final SwitchPoint[] spsArray = sps.isEmpty() ? null : sps.toArray(new SwitchPoint[sps.size()]); + + return new GuardedInvocation( + boundHandle, + guard == null ? + getFunctionGuard( + this, + cf.getFlags()) : + guard, + spsArray, + exceptionGuard); + } + + private GuardedInvocation createApplyOrCallCall(final boolean isApply, final CallSiteDescriptor desc, final LinkRequest request, final Object[] args) { + final MethodType descType = desc.getMethodType(); + final int paramCount = descType.parameterCount(); + if(descType.parameterType(paramCount - 1).isArray()) { + // This is vararg invocation of apply or call. This can normally only happen when we do a recursive + // invocation of createApplyOrCallCall (because we're doing apply-of-apply). In this case, create delegate + // linkage by unpacking the vararg invocation and use pairArguments to introduce the necessary spreader. + return createVarArgApplyOrCallCall(isApply, desc, request, args); + } + + final boolean passesThis = paramCount > 2; + final boolean passesArgs = paramCount > 3; + final int realArgCount = passesArgs ? paramCount - 3 : 0; + + final Object appliedFn = args[1]; + final boolean appliedFnNeedsWrappedThis = needsWrappedThis(appliedFn); + + //box call back to apply + CallSiteDescriptor appliedDesc = desc; + final SwitchPoint applyToCallSwitchPoint = Global.getBuiltinFunctionApplySwitchPoint(); + //enough to change the proto switchPoint here + + final boolean isApplyToCall = NashornCallSiteDescriptor.isApplyToCall(desc); + final boolean isFailedApplyToCall = isApplyToCall && applyToCallSwitchPoint.hasBeenInvalidated(); + + // R(apply|call, ...) => R(...) + MethodType appliedType = descType.dropParameterTypes(0, 1); + if (!passesThis) { + // R() => R(this) + appliedType = appliedType.insertParameterTypes(1, Object.class); + } else if (appliedFnNeedsWrappedThis) { + appliedType = appliedType.changeParameterType(1, Object.class); + } + + /* + * dropArgs is a synthetic method handle that contains any args that we need to + * get rid of that come after the arguments array in the apply case. We adapt + * the callsite to ask for 3 args only and then dropArguments on the method handle + * to make it fit the extraneous args. + */ + MethodType dropArgs = MH.type(void.class); + if (isApply && !isFailedApplyToCall) { + final int pc = appliedType.parameterCount(); + for (int i = 3; i < pc; i++) { + dropArgs = dropArgs.appendParameterTypes(appliedType.parameterType(i)); + } + if (pc > 3) { + appliedType = appliedType.dropParameterTypes(3, pc); + } + } + + if (isApply || isFailedApplyToCall) { + if (passesArgs) { + // R(this, args) => R(this, Object[]) + appliedType = appliedType.changeParameterType(2, Object[].class); + // drop any extraneous arguments for the apply fail case + if (isFailedApplyToCall) { + appliedType = appliedType.dropParameterTypes(3, paramCount - 1); + } + } else { + // R(this) => R(this, Object[]) + appliedType = appliedType.insertParameterTypes(2, Object[].class); + } + } + + appliedDesc = appliedDesc.changeMethodType(appliedType); //no extra args + + // Create the same arguments for the delegate linking request that would be passed in an actual apply'd invocation + final Object[] appliedArgs = new Object[isApply ? 3 : appliedType.parameterCount()]; + appliedArgs[0] = appliedFn; + appliedArgs[1] = passesThis ? appliedFnNeedsWrappedThis ? ScriptFunctionData.wrapThis(args[2]) : args[2] : ScriptRuntime.UNDEFINED; + if (isApply && !isFailedApplyToCall) { + appliedArgs[2] = passesArgs ? NativeFunction.toApplyArgs(args[3]) : ScriptRuntime.EMPTY_ARRAY; + } else { + if (passesArgs) { + if (isFailedApplyToCall) { + final Object[] tmp = new Object[args.length - 3]; + System.arraycopy(args, 3, tmp, 0, tmp.length); + appliedArgs[2] = NativeFunction.toApplyArgs(tmp); + } else { + assert !isApply; + System.arraycopy(args, 3, appliedArgs, 2, args.length - 3); + } + } else if (isFailedApplyToCall) { + appliedArgs[2] = ScriptRuntime.EMPTY_ARRAY; + } + } + + // Ask the linker machinery for an invocation of the target function + final LinkRequest appliedRequest = request.replaceArguments(appliedDesc, appliedArgs); + + GuardedInvocation appliedInvocation; + try { + appliedInvocation = Bootstrap.getLinkerServices().getGuardedInvocation(appliedRequest); + } catch (final RuntimeException | Error e) { + throw e; + } catch (final Exception e) { + throw new RuntimeException(e); + } + assert appliedRequest != null; // Bootstrap.isCallable() returned true for args[1], so it must produce a linkage. + + final Class<?> applyFnType = descType.parameterType(0); + MethodHandle inv = appliedInvocation.getInvocation(); //method handle from apply invocation. the applied function invocation + + if (isApply && !isFailedApplyToCall) { + if (passesArgs) { + // Make sure that the passed argArray is converted to Object[] the same way NativeFunction.apply() would do it. + inv = MH.filterArguments(inv, 2, NativeFunction.TO_APPLY_ARGS); + } else { + // If the original call site doesn't pass argArray, pass in an empty array + inv = MH.insertArguments(inv, 2, (Object)ScriptRuntime.EMPTY_ARRAY); + } + } + + if (isApplyToCall) { + if (isFailedApplyToCall) { + //take the real arguments that were passed to a call and force them into the apply instead + Context.getContextTrusted().getLogger(ApplySpecialization.class).info("Collection arguments to revert call to apply in " + appliedFn); + inv = MH.asCollector(inv, Object[].class, realArgCount); + } else { + appliedInvocation = appliedInvocation.addSwitchPoint(applyToCallSwitchPoint); + } + } + + if (!passesThis) { + // If the original call site doesn't pass in a thisArg, pass in Global/undefined as needed + inv = bindImplicitThis(appliedFn, inv); + } else if (appliedFnNeedsWrappedThis) { + // target function needs a wrapped this, so make sure we filter for that + inv = MH.filterArguments(inv, 1, WRAP_THIS); + } + inv = MH.dropArguments(inv, 0, applyFnType); + + /* + * Dropargs can only be non-()V in the case of isApply && !isFailedApplyToCall, which + * is when we need to add arguments to the callsite to catch and ignore the synthetic + * extra args that someone has added to the command line. + */ + for (int i = 0; i < dropArgs.parameterCount(); i++) { + inv = MH.dropArguments(inv, 4 + i, dropArgs.parameterType(i)); + } + + MethodHandle guard = appliedInvocation.getGuard(); + // If the guard checks the value of "this" but we aren't passing thisArg, insert the default one + if (!passesThis && guard.type().parameterCount() > 1) { + guard = bindImplicitThis(appliedFn, guard); + } + final MethodType guardType = guard.type(); + + // We need to account for the dropped (apply|call) function argument. + guard = MH.dropArguments(guard, 0, descType.parameterType(0)); + // Take the "isApplyFunction" guard, and bind it to this function. + MethodHandle applyFnGuard = MH.insertArguments(IS_APPLY_FUNCTION, 2, this); //TODO replace this with switchpoint + // Adapt the guard to receive all the arguments that the original guard does. + applyFnGuard = MH.dropArguments(applyFnGuard, 2, guardType.parameterArray()); + // Fold the original function guard into our apply guard. + guard = MH.foldArguments(applyFnGuard, guard); + + return appliedInvocation.replaceMethods(inv, guard); + } + + /* + * This method is used for linking nested apply. Specialized apply and call linking will create a variable arity + * call site for an apply call; when createApplyOrCallCall sees a linking request for apply or call with + * Nashorn-style variable arity call site (last argument type is Object[]) it'll delegate to this method. + * This method converts the link request from a vararg to a non-vararg one (unpacks the array), then delegates back + * to createApplyOrCallCall (with which it is thus mutually recursive), and adds appropriate argument spreaders to + * invocation and the guard of whatever createApplyOrCallCall returned to adapt it back into a variable arity + * invocation. It basically reduces the problem of vararg call site linking of apply and call back to the (already + * solved by createApplyOrCallCall) non-vararg call site linking. + */ + private GuardedInvocation createVarArgApplyOrCallCall(final boolean isApply, final CallSiteDescriptor desc, + final LinkRequest request, final Object[] args) { + final MethodType descType = desc.getMethodType(); + final int paramCount = descType.parameterCount(); + final Object[] varArgs = (Object[])args[paramCount - 1]; + // -1 'cause we're not passing the vararg array itself + final int copiedArgCount = args.length - 1; + final int varArgCount = varArgs.length; + + // Spread arguments for the delegate createApplyOrCallCall invocation. + final Object[] spreadArgs = new Object[copiedArgCount + varArgCount]; + System.arraycopy(args, 0, spreadArgs, 0, copiedArgCount); + System.arraycopy(varArgs, 0, spreadArgs, copiedArgCount, varArgCount); + + // Spread call site descriptor for the delegate createApplyOrCallCall invocation. We drop vararg array and + // replace it with a list of Object.class. + final MethodType spreadType = descType.dropParameterTypes(paramCount - 1, paramCount).appendParameterTypes( + Collections.<Class<?>>nCopies(varArgCount, Object.class)); + final CallSiteDescriptor spreadDesc = desc.changeMethodType(spreadType); + + // Delegate back to createApplyOrCallCall with the spread (that is, reverted to non-vararg) request/ + final LinkRequest spreadRequest = request.replaceArguments(spreadDesc, spreadArgs); + final GuardedInvocation spreadInvocation = createApplyOrCallCall(isApply, spreadDesc, spreadRequest, spreadArgs); + + // Add spreader combinators to returned invocation and guard. + return spreadInvocation.replaceMethods( + // Use standard ScriptObject.pairArguments on the invocation + pairArguments(spreadInvocation.getInvocation(), descType), + // Use our specialized spreadGuardArguments on the guard (see below). + spreadGuardArguments(spreadInvocation.getGuard(), descType)); + } + + private static MethodHandle spreadGuardArguments(final MethodHandle guard, final MethodType descType) { + final MethodType guardType = guard.type(); + final int guardParamCount = guardType.parameterCount(); + final int descParamCount = descType.parameterCount(); + final int spreadCount = guardParamCount - descParamCount + 1; + if (spreadCount <= 0) { + // Guard doesn't dip into the varargs + return guard; + } + + final MethodHandle arrayConvertingGuard; + // If the last parameter type of the guard is an array, then it is already itself a guard for a vararg apply + // invocation. We must filter the last argument with toApplyArgs otherwise deeper levels of nesting will fail + // with ClassCastException of NativeArray to Object[]. + if(guardType.parameterType(guardParamCount - 1).isArray()) { + arrayConvertingGuard = MH.filterArguments(guard, guardParamCount - 1, NativeFunction.TO_APPLY_ARGS); + } else { + arrayConvertingGuard = guard; + } + + return ScriptObject.adaptHandleToVarArgCallSite(arrayConvertingGuard, descParamCount); + } + + private static MethodHandle bindImplicitThis(final Object fn, final MethodHandle mh) { + final MethodHandle bound; + if(fn instanceof ScriptFunction && ((ScriptFunction)fn).needsWrappedThis()) { + bound = MH.filterArguments(mh, 1, SCRIPTFUNCTION_GLOBALFILTER); + } else { + bound = mh; + } + return MH.insertArguments(bound, 1, ScriptRuntime.UNDEFINED); + } /** * Used for noSuchMethod/noSuchProperty and JSAdapter hooks. @@ -566,7 +912,7 @@ public abstract class ScriptFunction extends ScriptObject { * These don't want a callee parameter, so bind that. Name binding is optional. */ MethodHandle getCallMethodHandle(final MethodType type, final String bindName) { - return pairArguments(bindToNameIfNeeded(bindToCalleeIfNeeded(getBestInvoker(type, null)), bindName), type); + return pairArguments(bindToNameIfNeeded(bindToCalleeIfNeeded(data.getGenericInvoker(scope)), bindName), type); } private static MethodHandle bindToNameIfNeeded(final MethodHandle methodHandle, final String bindName) { @@ -594,8 +940,13 @@ public abstract class ScriptFunction extends ScriptObject { * * @return method handle for guard */ - private static MethodHandle getFunctionGuard(final ScriptFunction function) { + private static MethodHandle getFunctionGuard(final ScriptFunction function, final int flags) { assert function.data != null; + // Built-in functions have a 1-1 correspondence to their ScriptFunctionData, so we can use a cheaper identity + // comparison for them. + if (function.data.isBuiltin()) { + return Guards.getIdentityGuard(function); + } return MH.insertArguments(IS_FUNCTION_MH, 1, function.data); } @@ -623,10 +974,17 @@ public abstract class ScriptFunction extends ScriptObject { return self instanceof ScriptFunction && ((ScriptFunction)self).data == data && arg instanceof ScriptObject; } + //TODO this can probably be removed given that we have builtin switchpoints in the context + @SuppressWarnings("unused") + private static boolean isApplyFunction(final boolean appliedFnCondition, final Object self, final Object expectedSelf) { + // NOTE: we're using self == expectedSelf as we're only using this with built-in functions apply() and call() + return appliedFnCondition && self == expectedSelf; + } + @SuppressWarnings("unused") private static Object[] addZerothElement(final Object[] args, final Object value) { // extends input array with by adding new zeroth element - final Object[] src = (args == null)? ScriptRuntime.EMPTY_ARRAY : args; + final Object[] src = args == null? ScriptRuntime.EMPTY_ARRAY : args; final Object[] result = new Object[src.length + 1]; System.arraycopy(src, 0, result, 1, src.length); result[0] = value; @@ -642,14 +1000,12 @@ public abstract class ScriptFunction extends ScriptObject { } } - private static MethodHandle findOwnMH(final String name, final Class<?> rtype, final Class<?>... types) { - final Class<?> own = ScriptFunction.class; - final MethodType mt = MH.type(rtype, types); - try { - return MH.findStatic(MethodHandles.lookup(), own, name, mt); - } catch (final MethodHandleFactory.LookupException e) { - return MH.findVirtual(MethodHandles.lookup(), own, name, mt); - } + private static MethodHandle findOwnMH_S(final String name, final Class<?> rtype, final Class<?>... types) { + return MH.findStatic(MethodHandles.lookup(), ScriptFunction.class, name, MH.type(rtype, types)); + } + + private static MethodHandle findOwnMH_V(final String name, final Class<?> rtype, final Class<?>... types) { + return MH.findVirtual(MethodHandles.lookup(), ScriptFunction.class, name, MH.type(rtype, types)); } } |